有以下程序:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
*p = 10;
p++;
*p = 20;
printf("十进制 %d,%d\n",arr[0],arr[1]);
printf("十六进制 %08x,%08x\n",arr[0],arr[1]);
return 0;
}
我们暂时先不要去考虑其答案,而是先来探究中间这个“p++”在这里是什么意思?
在该程序中,我们定义了一个整型数组,然后定义了一个整形指针变量,其指向的是该数组的首元素地址,所以之后对其解引用“*p=10”,自然会使arr[0]=10,这是没有问题的。之后重点来了,这里的“p++”到底给p加了个什么东西?我们有以下猜想:
1. 加了整个数组的长度?
2. 加了一个字节的长度?
3. 加了四个字节的长度?
首先我们很容易把 1否决,因为这是不可能的,如果一下子给P加整个数组的长度,p就会从数组的最前端直接跳到末尾,这种结果对我们来说是毫无意义的。
那么 2 就有点意思了,加一个字节的长度会发生什么呢?
我们知道,整形数组中,每个元素需要用四个字节来保存,我们用下面的图像来简略表示arr[0]= 10和arr[1] = 2
其中,我们用红色字体假设其地址,每个格子所占空间为四个字节。接下来我们把这个图片放大四倍,即每个格子所占空间变为一个字节。如图:
之后我们便需要把10和2分别放入四个格子中,这其实很简单。
例如10,由于是四个格子,每个格子一个字节,即要用32位表示10 ,但每个格子写8个数字太麻烦了,所以我们用十六进制 表示10,这样刚好每两个十六进制数字放一个格子,我们知道10的十六进制是00 00 00 0a . 同理得出 2 的十六进制 00 00 00 02 .
最后重要的来了,是不是直接把这八对十六进制数字按次序放进去呢?当然不是!因为存在大小端的问题!低地址放大数据是大端,低地址放小数据是小端,而我们的PC正好是小端,所以要在低地址处放入小的数据。那么0x0000000a和0x00000002应该怎么放呢?如下图:
也许有人不明白,举个例子:十进制的10007中1和7 谁大?你可能会说 7 大,但你不能只看数字而不看位数啊,7是7,可这里的1是10000啊,所以如果也要把10007按小端储存,就应该把 7 放到最前面 ,1放到最后面。所以你现在明白为甚么上面的十六进制是为什么这么储存的了吧?
然后我们继续,让一级指针 p 加一个字节,那么解引用后赋予的值应该在下图绿框的地方,
即我们要把20放入绿框中,有了上面的经验,步骤就很清晰了,首先,20的十六进制是00 00 00 14,然后按照小端低地址放小数据的规则,有以下结果:
看到这里,想必大家已经知道,“加一个字节”这个猜想是绝对站不住脚的,“p++”绝对不可能整出这么复杂的玩意儿出来,只加一个字节,只会使指针错位。不过,既然算到了这里,我们就来看看这真正只加一个字节会对结果产生什么影响,接下来的程序如下:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
*p = 10;
p = (int *)((int)p+1); //该语句是让 p 只加一个字节
*p = 20;
printf("%08x,%08x\n",arr[0],arr[1]); //输出arr[0] 、arr[1] 的八位十六进制
return 0;
}
其输出结果为0000140a,00000000
别惊讶为甚么是反的…..因为你储存是按照小端储存,输出是正常的啊….
该结果跟我们的计算结果是一样的,有的同学还想知道其十进制的值,只要把上面输出语句里的“%08x”改为“%d”即可,其输出结果是5130,0
这里就可以更直观地看出其不靠谱了,10和2怎么可能变成这么大的数字?
那么最后,我们再回过头看本篇文章开头的那道程序,结果我就在这里公布了
十进制 10,20
十六进制0000000a,00000014
看到这两个十六进制数字,是不是感到莫名亲切?
总结:指针与数字间的加法,需要进行调整,其调整权重为指针所指向的类型长度,如:上面的例子,其权重就为sizeof(int)
总的计算式为:指针+(数字 * 权重)。
指针与数字间的减法,与指针加法类似,在此不赘述。其总的计算式为:指针 -(数字 * 权重)
接下来,我们讨论指针与指针之间的运算。
首先,我们需要知道的是,每一个运算都有其意义,例如:指针和一个数字进行加减运算,其意义是改变了指针指向的地址,使其严格指向下一个数据。而我们现在需要考虑指针之间的运算是什么含义?
我们先看 “指针+指针” ,这个运算有意义吗?显然是没有的,我们拿时间来作比喻,下午三点加下午四点得出来的值是什么?你肯定会是七,但这个值有意义吗?就算你说这个七是七点的意思,但时钟里每一个时刻都是独立的,两个时刻相加,你只是把他们的值加起来,就其本身而言并无意义。所以指针之间不存在加法运算。
然后我们看 “指针-指针”,还是拿时间来举例子,下午七点,减去下午三点,得出来的值是什么含义? 答案是四。你能说这代表的是四点这个时刻吗?并不是,这个四代表的是,三点和七点之间间隔着四个小时。
到这里指针之间减法的意义就呼之欲出了,两个指针相减得出的应该是两者之间元素的个数。
指针之间相减其计算式为:(指针 - 指针)/权重
下面是关于指针运算的程序,仅作为练习题,其结果已写明,请自行对照。
指针 + 数字:
int main()
{
int *p = (int *)2000;
printf("%d\n",p+4);//2016
printf("%d\n",(short *)p+4);//2008
printf("%d\n",(double *)p+4);//2032
printf("%d\n",(float **)p+4);//2016
printf("%d\n",(unsigned short *)p+4);//2008
printf("%d\n",(long *)p+4);//2016
printf("%d\n",(char *)p+4);//2004
printf("%d\n",(unsigned long long)p+4);//2004
return 0;
}
指针 - 数字:
int main()
{
int *p = (int *)0x2010;
printf("%x\n",p-2);//2008
printf("%x\n",(short *)p-2);//200c
printf("%x\n",(unsigned long *)p-2);//2008
printf("%x\n",(long long **)p-2);//2008
printf("%x\n",(float *)p-2);//2008
printf("%x\n",(double *)p-2);//2000
printf("%x\n",(char *)p-2);//200e
printf("%x\n",(unsigned long )p-2);//200e
return 0;
}
指针 - 指针:
int main()
{
int arr[10] = {1};
int *p = &arr[1];
int *q = &arr[9];
printf("%d\n",p-q);//-8
printf("%d\n",q-p);//8
printf("%d\n",(short *)q-(short *)p);//16
printf("%d\n",(double *)q-(double *)p);//4
printf("%d\n",(int ***)q-(int ***)p);//8
printf("%d\n",(char **)q-(char **)p);//8
printf("%d\n",(long)q-(long)p);//32
return 0;
}
其结果皆为标准答案,若不符请仔细思考,请特别注意*的存在与否以及各类型的字节数!