很多同学在学完指针后,弄懂了「*」和「&」的含义,又写了几个字符串相关的编程题,再刷几道指针笔试题,感觉指针精通了。其实C语言中的指针真是博大精深,他的复杂程度绝对超过你的想象。比如前两天一个同学来问我的问题,先看下代码:
#include <stdio.h>
int main()
{
const char *ptr[] = {"Welcome", "to", "Beautiful", "Nanjing"};
const char **p = ptr + 1;
ptr[0] = (*p++) + 2;
ptr[1] = *(p + 1);
ptr[2] = p[1] + 3;
ptr[3] = p[0] + (ptr[2] - ptr[1]);
printf("%s\n", ptr[0]);
printf("%s\n", ptr[1]);
printf("%s\n", ptr[2]);
printf("%s\n", ptr[3]);
return 0;
}
这是一个典型的指针笔试题。我见过大部分的同学做笔试题都擅长使用 「观察法」 ,如果这道题能用「观察法」得出结果,算我输!
代码就跟数学题一样,还是要动手算的。所以我们这篇文章就来详细的剖析一下。
STEP01
我们先来解决这行代码:
const char *ptr[] = {"Welcome", "to", "Beautiful", "Nanjing"};
很显然,定义了一个指针数组,对应的内存模型是这样的:
ptr是个数组,里面有4个指针,分别指向了内存里面的四个字符串。如果这个都没看懂的话…
那接下来就只能看个热闹了!
STEP02
下一行代码:
const char **p = ptr + 1;
这个就有点意思了,定义了一个指针的指针。首先要弄懂,「ptr + 1」是啥?ptr是数组的名字,也是数组的首地址,所以「ptr + 1」应该是数组第二个元素的地址,即0x2000的地址,看图:
所以,p的值就是0x104。至于说为什么p要定义成指针的指针,写成指针不行吗?还真不行,因为0x104这块内存里面保存的是一个地址,也就是说0x2000这个数据是「char *」类型。
STEP03
下面轮到几个赋值语句了,一行一行看:
ptr[0] = (*p++) + 2;
首先要搞懂「p++」。因为是后置++操作,先不管他。p指向数组第二个元素,那么p就是数组第二个元素,即0x2000。0x2000它是个指针,就是「char *」类型,步长是1个字节,所以加2结果是0x2002,也就是字符串“To”后面的那个字节,说白了就是字符’\0’。因为是个赋值语句,所以ptr[0]变成了0x2000。最后别忘了指针p向后加一。p指向数组第二个元素,加一的话指向了数组第三个元素,于是他们之间的关系变成了:
STEP04
继续:
ptr[1] = *(p + 1);
p现在的值是0x108,所以「p + 1」变成了0x10c,对这个地址取值,就是0x10c内存里面保存的数据0x1500,赋值给ptr[1],于是关系又变成了:
STEP05
再一次赋值:
ptr[2] = p[1] + 3;
p指向数组第三个元素,那么p[1]就是从第三个元素开始,向后数1个元素,就是数组的第四个元素,所以p[1]的数值应该是0x1500,0x1500是「char *」类型,步长一个字节。所以加3结果就是0x1503,不就是’j’的地址吗!
STEP06
最后一次赋值:
ptr[3] = p[0] + (ptr[2] - ptr[1]);
这条语句就比较「贱」了,明摆着故意刁难学生!p[0]就是指针p指向内存的值,即0x1503。「ptr[2] - ptr[1]」看起来太吓人了。ptr[2]就是0x1503,ptr[1]就是0x1500,这两个类型一样,都是 「char *」类型,步长为1,所以你就把它当作数字运算就行了,结果是3。因为p[0]也是「char *」类型,所以加3就变成了0x1506,就是字符’g’的地址。
STEP07
最后呢,就是把这些地址指向的内存数据打印出来,结果是:
分析就到这。最后要提醒大家:
1、不管多么复杂的指针运算,「观察法」解决不了问题(智商过140的除外!),还是要动手画图;
2、指针可以当作数组使用,数组也可以当作指针使用,单纯从使用的角度来看,指针和数组没有区别(注意,是使用的角度,不是说指针和数组一样没有区别);
3、不管是指针还是指针的指针,不要乱了阵脚,搞清楚它们的步长、性质。
更多文章、视频、嵌入式学习资料,微信关注公众号 『学益得智能硬件』