int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
这一道面试题,可以分为两个部分
第一部分为变量的声明
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
第二部分为一些输出
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
第二部分的重点,我觉得是后面的表达式,先不谈后方的表达式。
诺想理解函数后面的表达式,需先将上面的三个指针变量的声明与关系处理明白
指针的声明部分
char *c[] = {"ENTER","NEW","POINT","FIRST"};
第一条语句是 字符串常量数组的声明
对声明部分进行分析,会稍微有些奇怪。
c是一个数组,数组中存储的元素类型是 char* ,那么理论上数组中存放的应该为字符指针
即字符的地址。而数组后的大括号中却是四个字符串。
其实c数组中存储的其实为 每个字符串的首元素地址
第二天条语句是 二级指针数组的声明
char**cp[] = {c+3,c+2,c+1,c};
第二条语句的核心是 c变量,只要理解了c变量,整个语句便清晰了。
c变量是什么?
c是数组名,非特殊条件下为数组首元素的地址,即char* c[] 数组中首元素的地址。
首元素为字符串 “ENTER” 的首元素地址,也就是说c是首元素地址的地址,为二级指针。
反观声明,第二条语句声明的也是 二级指针数组 。
指针与正数的加减运算,会影响指向
因c变量是字符指针,进行运算的步长为1个字节。c+1指向的便是 c数组中第二个元素的地址
c+2 c+3 同理
注:图中的连线紧代表cp中指针的指向
第三条语句是三级指针的声明
char***cpp = cp;
cp是 char** cp[]的数组名,即数组的首元素地址
画出三个指针函数间的关系图
输出部分
第一条输出语句
printf("%s\n", **++cpp);
第一条输出语句的关键:**++cpp
该如理解这条语句呢?
先从变量名入手,cpp是一个指针,++cpp等于指针前移一个步长,即
目前而言,指针cpp中存储的是c+2的地址(cp数组中第二个元素的地址)。
对cpp进行解引用便得到cp数组中第二个元素
cp中存储的为一些c数组中元素的地址。
按照图中所示关系
*++cpp 等价于 &c[2]
那么**++cpp 等价于 *&c[2] 也就是 c[2]
c中存放都是一些地址,字符串首元素的地址。
故c[2]所得到的其实就是"POINT"中P的地址
总而言之 就是将 p 的地址 传给了printf函数
第二条输出语句
printf("%s\n", *--*++cpp+3);
这条语句的赋值程度远胜第一条语句,想要理解 *--*++cpp+3
需要先从操作符优先级下手
++(前置) > --(前置) > * > +(加法)
从变量cpp入手,cpp是指针变量,前置++。先赋值再使用,即cpp前移一个步长
即*++cpp的得到 c+1,*--*++cpp+3 等价于 *--(c+1)+3
c+1也是一个指针变量, 前置--。先赋值再使用,即c+1后退一个步长
简单表达为 c.
总的来说,变是cp中第三个元素 c+1 便为 c
故--*++cpp等价于 c
c是什么呢?c是数组char* c[]的数组名。也就是说是数组的首元素地址
故*--*++cpp的到的即是数组c的首元素。
就像上文分析的,数组c中存放的是常量字符串的首元素地址,
也就是说,现在拿到了字符串 ”ENTER"中E的地址。
即 *--*++cpp即为”ENTER"中E的地址。
E的地址是char* 类型的,+3前进三个字节。得到的便是第二个E的地址。
故整个表达式*--*++cpp+3所表示的即为”ENTER"中第二个E的地址。
第三条输出语句
printf("%s\n", *cpp[-2]+3);
同样是先从变量入手,*cpp[-2]。
[] 的结合优先级高于 * ,而又像之前提到的 a[i] 等价于 *(a+i)
故 *cpp[-2] 等价于 **(cpp-2);
由图可知,*(cpp-2)取到的起始是 c+3
那么 **(cpp-2) 等价于 *(c+3)
*(c+3) 可以转化成 c[3]
也就是说 *cpp[-2] 也就是 c数组中下标为3的元素
c中存放的均是字符串常量的首字符地址
也要注意,目前*c[-2]是一个字符的地址,也就是字符指针。
字符指针加减运算的步长是1,故*c[-2]+3最后指向的是 “FIRST” 中S的地址
第四条语句
printf("%s\n", cpp[-1][-1]+1);
还是从变量入手,与上一条语句同样的分析逻辑,cpp[-1] 等价于 *(cpp-1)
故*(cpp-1)便取到了c+2
即 cpp[-1][-1]等价于(c+2)[-1]
同理 (c+2)[-1]等价于*(c+1)
即*(c+1)便取到了数组c中下标为1的元素
即"NEW"中N的地址
综上所述
cpp[-1][-1]便是 N的地址
cpp[-1][-1]+1 也就是 E的地址