内存
物理存储器和存储地址空间
物理存储器:实际存在的具体存储器芯片。比如:内存条、RAM芯片、ROM芯片。
存储地址空间:对存储器编码的范围。
- 编码:对每个物理存储单元(一个字节)分配一个号码
- 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写
内存地址
- 将内存抽象成一个很大的一维字符数组。
- 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)。
- 这个内存编号我们称之为内存地址。
内存中的每一个数据都会分配相应的地址: - char:占一个字节分配一个地址
- int: 占四个字节分配四个地址
- float、struct、函数、数组等
指针和指针变量
-
指针就是地址,地址就是指针
int a; int b[]; int* p;
上述代码中
&a
、和b
和p
都是地址,也都是指针。 -
指针变量是存放地址的变量
-
通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样
指针的基础知识
指针变量的定义和使用
定义指针的方式如下:
数据类型* 变量名
其中*
表示该变量为指针变量,数据类型表示该指针变量保存的是哪一种数据类型的地址,也就是指针变量指向哪一种数据类型。
访问指针变量指向的内存空间的数据:
*指针变量
此处的*
为取值运算符。
指针变量保存的是内存地址,*指针变量
就表示访问该地址对应的内存空间。对*指针变量
操作,其实就是读写该内存空间的数据。
#include<stdio.h>int main() {int a = 10;int* p;p = &a;printf("a = %d\n", a);printf("p指向的数据=%d\n", *p);printf("变量a的地址:%p\n", &a);printf("指针变量p存的地址:%p\n", p);return 0;
}
运行上面代码,结果如下:
注意:&
可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
使用指针访问数据的原理
不同类型的数据在内存中占用不同的字节数,指针变量保存的是数据首个字节的地址。系统根据指针变量保存的地址找到首个字节后,再根据指针指向的数据类型,顺位读取不同的字节长度,从而访问到完整的数据。
比如,一个int
型变量a
占4字节,指针变量int* p
只保存了a
在内存中的首个字节的地址。当操作*p
时,系统会先根据该地址找到首个字节,然后再顺位读取3个字节,总共读取了4个字节,从而取出变量a
的值。
通过指针间接修改变量的值
#include<stdio.h>int main() {int a = 10;int* p;p = &a;printf("a = %d\n", a);printf("p指向的数据=%d\n", *p);printf("变量a的地址:%p\n", &a);printf("指针变量p存的地址:%p\n", p);*p = *p + 1;printf("p指向的数据加1后,a = %d\n", a);printf("p指向的数据加1后,p指向的数据=%d\n", *p);return 0;
}
运行上面的代码,结果如下:
在定义指针类型时一定要和指向的变量类型是一样的才行。
#include<stdio.h>int main() {char a = 97;int* p = &a;printf("a的值=%d\n", a);printf("*p的值=%d\n", *p);return 0;
}
运行上面的代码,结果如下:
上面代码中,虽然指针变量p
保存的是字符变量a
的地址,但是p
是指向int型数据的,所以在找到a
的地址后还会顺位读取3个字节的数据。
指针大小
windows中数据存储采用小端对齐的方式。也就是,数据的低位放在地址小的内存中。
指针也是一种数据类型,所以可以使用sizeof()
测量指针的大小,得到的总是:4或8。
这是因为指针存储的是内存地址。在32位处理器中内存地址是一个32位的编号,在64位处理器中内存地址是一个64位的编号。
所以在32位处理器中sizeof(指针)
得到的总是4,在64位处理器中sizeof(指针)
得到的总是8。
在visio studio中可以在下图中选择基于32位或64位的处理器编译,从而使sizeof(指针)
得到不同的结果。
int main() {int* p1;int** p2;char* p3;char** p4;printf("sizeof(p1) = %d\n", sizeof(p1));printf("sizeof(p2) = %d\n", sizeof(p2));printf("sizeof(p3) = %d\n", sizeof(p3));printf("sizeof(p4) = %d\n", sizeof(p4));printf("sizeof(double *) = %d\n", sizeof(double*));return 0;
}
上述代码基于32位处理器编译运行后结果如下:
上述代码基于64位处理器编译运行后结果如下:
野指针和空指针
野指针
任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统可能不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域可能会出问题。
int main() {int* p = 100;/*操作系统将0~255的内存空间作为系统占用,不允许被访问操作*/printf("%d\n", *p);return 0;
}
空指针
为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL
赋值给此指针,这样就标志此指针为空指针。
int *p = NULL;
NULL
是一个值为0的宏常量:
#define NULL ((void *)0)
空指针可用于条件判断
int* p = NULL;if (p == NULL) {printf("我是一个空指针\n");}else {printf("我不是一个空指针\n");}
万能指针void *
万能指针可以接收任意数据类型的地址。但不能直接访问万能指针指向的内存空间,因为系统找到数据的首个字节后,不知道接下来要顺位读取多少个字节。
必须先将万能指针强转为某个具体类型的指针变量后才可以访问。
#include<stdio.h>int main() {int a = 10;void* p = &a;printf("变量a的值:%d\n", a);//printf("万能指针p指向的值:%d\n", *p);//不能直接访问万能指针指向的内存空间printf("万能指针p指向的值:%d\n", *(int*)p);*(int*)p = *(int*)p + 1;printf("加1后,变量a的值:%d\n", a);printf("加1后,万能指针p指向的值:%d\n", *(int*)p);return 0;
}
执行上面代码,结果如下:
const修饰的指针变量
const 数据类型* 变量名
:const
修饰数据类型,说明该指针变量指向的内存空间可读不可写。
数据类型* const 变量名
:const
修饰变量名,说明该指针变量保存的内存地址可读不可写。
const 数据类型* const 变量名
:const
既修饰数据类型又修饰变量名,说明该指针变量指向的内存空间可读不可写,保存的内存地址也是可读不可写的。
#include<stdio.h>int main() {const int a = 10;//a = 20;//errint b = 20;const int* p = &a;printf("指针变量p指向的值:%d\n",*p);//*p = 30;//errp = &b;//okprintf("指针变量p指向的值:%d\n", *p);int* const p2 = &a;printf("指针变量p2指向的值:%d\n", *p2);//p2 = &b;//err*p2 = 30;//okprintf("指针变量p2指向的值:%d\n", *p2);printf("a的值:%d\n", a);const int* const p3 = &b;//*p3 = 40;//err//p3 = &a;//errprintf("指针变量p3指向的值:%d\n", *p3);b = 40;printf("指针变量p3指向的值:%d\n", *p3);return 0;
}
运行上面代码,结果如下:
const
只能限定它所修饰的指针变量,我们不能通过这个指针变量来修改指向的内存空间的数据或修改这个指针变量保存的内存地址。但是,我们能通过其它指针变量来修改它们。
#include<stdio.h>int main() {int a = 10;int b = 20;const int* p = &a;//*p = 20;//err 不能通过指针变量p改变a的值a = 30;//okprintf("a的值:%d\n", a);printf("*p的值:%d\n", *p);const int* const p2 = &a;int** p3 = &p2;//*p2 = 20;//err 不能通过指针变量p2改变a的值//p2 = &b;//err 不能改变指针变量p2的指向,即不能改变p2保存的内存地址**p3 = 40;//ok 通过指针变量p3改变a的值printf("通过指针变量p3改变a的值后,a的值:%d\n", a);printf("通过指针变量p3改变a的值后,*p2的值:%d\n", *p2);printf("通过指针变量p3改变a的值后, **p3的值:%d\n", **p3);*p3 = &b;//ok 通过指针变量p3改变指针变量p2的指向printf("通过指针变量p3改变指针变量p2的指向后,*p2的值:%d\n", *p2);printf("通过指针变量p3改变指针变量p2的指向后, **p3的值:%d\n", **p3);return 0;
}
运行上面的代码,结果如下: