指针是一个特殊的变量,因为他的运算方式和所代表的东西跟基础数据类型不一样。指针变量存储的是内存单元的地址编号,这地址就跟我们家门口的门牌号一样。当用解引用运算符(*)的时候,CUP就会到对应的内存单元取内容,至于取多少,主要看定义时定义的是什么类型的指针,也可以用下标运算符([])。
1.指针变量占用的字节大小都是一样的。
在相同位数的系统上,不同的指针变量占用的字节大小都是一样的,但会随着系统位数的增加而增加。因为指针指向的是内存地址,比如说32位系统上,最大能支持的内存单元个数是2^32,所以指针占用的字节数只需要4个字节(4*8=32)就能访问到系统上任意位置上的内存单元了。64位的指针就是8个字节如此类推。别误解了上说的32位系统就只是系统,同样也是32位软件。因为系统是向下兼容的,即使你是64位系统运行32位软件,指针也只是4个字节。
2.解引用 * 和下标运算符[]
解引用
"*"的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。也就是说,解引用是返回内存地址中保存的值。例:
int i = 0x12345678;
int *pi = &i;//等同于int *pi; pi = &i;
printf("&i = %x, i = %d, *pi = %d", &i, i, *pi);
分析:*pi其实是将pi的值0x1cfd48当作地址,然后到0x1cfd48内存地址上去取内容,由于pi是int *类型,那就按照int的方式去取,把0x1cfd48开始的四个字节内容取出来。如果把pi的类型改成short *,那么他只会取出0x5678。由于这是大端模式,所以可以看到数据是高位在前的。
n级指针
多少级指针,就看定义的时候有多少个*了。多级指针很少用,一般就只用到二级指针(**)。解多级指针只不过是在多个地址间转来转去,最后到达目的地取内容,解多级指针的时候要注意,每解一次取得内容都是地址,直到解到了第n次才是真实内容。
int i = 0x12345678;
int *pi = &i;
int **ppi = π//等同于int **ppi; ppi = π
printf("&i = %x, &pi = %x", &i, &pi);
printf("\nppi = %x, *ppi = %x, **ppi = 0x%x\n", ppi, *ppi, **ppi);
分析:ppi是二级指针,pi是一级指针,i是int变量。很明显看出,多级指针是用于保存低一级的指针变量的地址。ppi保存pi的地址,pi保存i的地址。要想从ppi去取出i的内容,就必须是先*ppi(到pi地址上取出pi的内容),再 *把从pi取出的内容当作地址去取内容(到i的地址上取出i的内容),所以这就是**ppi执行的过程。
//下面代码一样可以取出i的内容。通过把二级指针转成一级指针(从n级指针一级一级的往下转)
int i = 0x12345678;
int *pi = &i;
int **ppi = π
int *temp = *ppi;//等同于int *temp; temp= *ppi;间接等同于int *temp; temp= &i;
printf("%d", *temp);
下标运算符[]
跳转
下标运算符运算的时候,其实就是指针加法运算。
3. 指针的加减法运算
指针只有加减法算术运算,并没有乘除等算术运算,因为指针的算术运算是根据指针类型去运算,并不是普通的算术加减。
减法
可以理解成 两个地址之间的差值除去运算符左边的“类型”的字节数的结果向上取整。例:
char c;
char *pc = &c;//char *类型,保存char c的地址
//char *pc = 120;//可以替换掉上面两行代码,由于我并没有对这个地址做任何操作,所以并不会出现内存访问冲突错误,其他情况并不建议这么写,除非是要操作硬件
unsigned int pc_value = pc;//将pc的值赋给pc_value,方便计算相差几个字节
int i;
int *pi = &i;//int *类型,保存int i的地址
//int *pi = 520;//可以替换掉上面两行代码
unsigned int pi_value = pi;//将pi的值赋给pi_value,方便计算相差几个字节
//pi - pc,由于pi是int *类型的,所以是按照int的字节去计算,实际效果等同于(pi_value - pc_value)*1.0/sizeof(int)的结果向上取整。其他类型也如此。
printf("&c = %x, &i = %x\n&i - &c = %d\n&c - &i = %d\n%d",
pc, pi, pi - pc, pc - pi, pc_value - pi_value);
分析:0xeffcd7 - 0xeffcb0 = -39。也就是两个地址之间相差了39个字节。按四个字节来分,就是相差了10个这样的空间。按一个字节来分就是相差了39个。
加法
根据数学的逆运算,可以将减法变换成加法,(地址-地址=相差个数)变成(地址+相差个数=地址)。具体例子由你们自行去验证。
4.指针数组和数组指针
这两个有点绕,一不小心就容易弄错。其实从名字上来分的话,重点是后面两个字,指针数组的数组,数组指针的指针。
数组指针
就是指向一个数组的指针,通常都是用于指向二维以上的数组。以指向二维数组为例,定义格式一般为
类型 (*变量名)[数组个数]
int a[2][10] = { { 0,1,2,3,4,5,6,7,8,9 }, {10,11,12} };
//数组指针
int(*pArray)[10];
pArray = a;//只需要初始化一次就能访问二维数组a的任何元素了
//pArray[0] = a;//错误赋值方式,错误信息是必须可修改左值,因为这样的赋值方式等同于a[0] = 数组。
//int (*pArray)[10] = a;//等同上面两行代码,把a替换成a[0]和&a[0]效果一样
printf("&a[0][0] = %x, &a[1][0] = %x\npArray[0] = %x, pArray[1] = %x\n",
&a[0][0], &a[1][0], pArray[0], pArray[1]);
printf("a[0][0] = %d, a[1][0] = %d\npArray[0][0] = %d, pArray[1][0] = %d",
a[0][0], a[1][0], *pArray[0], pArray[1][0]);
//(*pArray)[0]等同于pArray[0][0],(*(pArray+1))[0]等同于pArray[1][0]
//*(*pArray)等同于pArray[0][0],*(*(pArray+1))等同于pArray[1][0]
/*************************************/
分析:1、定义一个指向数组长度为10的一维数组的指针pArray,指针pArray等价于一个二维数组。
2、从上面可以看出,数组指针用法其实就跟n维数组差不多。只是把n维数组的最高维换成了(*pArray)。
3、数组指针的地址偏移量其实是数组占用的内存大小,也就是10*sizeof(int),所以pArray[0]和pArray[1]相差了40。
/*************************************/
指针数组
一个保存指针的数组。也就是数组里面的值都是地址。定义格式一般为
类型 *变量名[数组个数]
int a[2][10] = { { 0,1,2,3,4,5,6,7,8,9 }, {10,11,12} };
//指针数组
//int *pArray2[10] = a;//指针数组的错误赋值方式,因为这是数组,初始化方式是{}
int *pArray2[10];//数组的元素必须要初始化,否则可能会出现操作非法内存的错误
//pArray2 = a;//错误的赋值方式,因为数组变量的变量名代表的是一个不可修改的值(等同于* const),只能修改数组的内容
pArray2[0] = a;
pArray2[1] = a[0];
pArray2[2] = &a[0][0];
pArray2[3] = a[1];
pArray2[4] = &a[1][0];
pArray2[5] = &a[1][2];
int i = 100;
pArray2[6] = &i;
printf("a = %x, a[0] = %x, &a[0][0] = %x\n", a, a[0], &a[0][0]);
printf("*pArray2[0] = %d, *pArray2[1] = %d, *pArray2[2] = %d\n", *pArray2[0], *pArray2[1], *pArray2[2]);
printf("*pArray2[3] = %d, *pArray2[4] = %d, *pArray2[5] = %d\n", *pArray2[3], *pArray2[4], *pArray2[5]);
printf("*pArray2[6] = %d\n", *pArray2[6]);
数组指针和指针数组加减法
这其实跟普通指针的加减法没什么区别,只是数组指针的字节大小是按数组的去计算而已。其他的并没有什么区别。大家自行验证。
任何类型的数组都可以转成任意的数组指针,但如果数组长度不能被数组指针的长度整除,那么就容易出现越界(操作非法地址)导致程序崩溃。确保整除了也会越界的问题,只要控制在数组长度范围内就没有问题了。反正只要是指针都会有可能出现这些问题,但可以避免的。
#include
#include
int main() {
int a[12] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
int(*pa2)[6] = a;//一维数组转二维数组, 12/6 = 2;所以pa2以二维数组方式去访问的时候,下标不能超过[1][5]
int(*pa3)[2][2] = a;//一维数组转三维数组, 12/(2x2) = 3;跟上同理
printf("a[11] = %d, pa2[1][5] = %d, pa3[2][1][1] = %d \n", a[11], pa2[1][5], pa3[2][1][1]);
//pa2: 1x6+5=11. pa3: 2x4+1x2+1=11.
int b[2][6] = { {0,1,2,3,4}, {5,6,7,8,9,10} };//12个元素
int *pb1 = b;//一维数组
int (*pb3)[2][2] = b;//三维数组
printf("\npb1[6] = %d, b[1][0] = %d, pb3[1][1][0] = %d \n", pb1[6], b[1][0], pb3[1][1][0]);
system("pause");
return 0;
}
既是指向一个已删除的对象或未申请访问受限内存区域的指针。没有一个有效判断一个指针是否是野指针的办法,因为野指针的指针变量的值并不是固定,也没有系统函数可以去判断,只有判断指针是否为空(NULL)。一旦对野指针进行操作,那就有可能面临程序出现错误退出的问题,如果操作的是不允许操作的地址就会出现这样的错误,所以并不是百分百出现错误。避免出现野指针,最好就是定义和释放内存的时候,将指针变量置空。避免出错就是在避免野指针的基础上在使用指针前判断指针是否为空指针,有些时候可能为了效率上的问题不加这判断,这就要求指针必须是有效的了。
上面有哪里说得不好的,不懂的欢迎大家提。大家一起来完善。