首先让我们看一段代码
#include<stdio.h>
int main()
{
const char* c[] = {"HELLO", "NEW", "WORLD", "SAYHI"};
const char** cp[] = {c + 3, c + 2, c + 1, c};
const char*** cpp = cp;
printf("%s,", **++cpp);
printf("%s,", *--*++cpp + 3);
printf("%s,", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
return0;
}
1、printf("%s,", **++cpp);
cpp是一个指向char**类型的指针,并且初始化为cp,即cpp指向cp[0]的位置(cp是一个指针数组,数组里面存储的类型为char**),所以*cpp=cp[0]。++cpp前++,所以printf输出的值为cp[1]的值;而cp[1]是一个指向c[2]的指针,因此**++cpp=*cp[1]=c[2],所以输出WORLD。
2、printf("%s,", *--*++cpp + 3);
由于第一个printf语句,这时cpp指向的是cp[1],所以++cpp使cpp指向cp[2],此时*++cpp=cp[2],而cp[2]是一个指向c[1]的指针。对指向c[1]的指针进行--操作,所以cp[2]指向的从c[1]变为c[0],所以*--*++cpp=c[0]。而c[0]存放的是指向HELLO字符串的指针,所以c[0]+3指向的是HELLO中第4个字母,所以输出为LO。
3、printf("%s,", *cpp[-2] + 3);
讲这句之前,先来说说数组名的含义
数组名其实就是个常量地址,类型 数组名[n],那么数组名就是 类型* 的地址。当我们使用数组作为函数的参数时,函数的形参就是指针变量,所以它的长度信息才丢失。arr[n] <=> *(arr+n) 这两种语法就是等价的,如果指针变量指向一块连续内存,指针变量可以当数组使用。
第二个printf结束后,cpp指向的是cp[2],cpp[-2]可以表达成*(cpp-2),所以*(cpp-2) = cp[0],cp[0]是一个指向c[3]的指针,解引用,即*cp[0]=c[3],所以**(cpp-2)=c[3]。而c[3]指向的是SAYHI,c[3]+3指向的是其中第4个字母,所以输出HI。
指针与数组名的相同点:
1、它们都是地址,代表一块连接的内存。
3、它们都可以使用*,[]进行解引用,遍历一块连续内存。
指针与数组名的不同点:
1、指针是变量而数组名是常量
2、指针变量有自己的存储空间用于存储内存地址,而数组名没有,它就是地址。
3、指针变量与目标内存是指向关系,而数组名与目标内存是映射关系。
4、printf("%s\n", cpp[-1][-1] + 1);
先介绍一下指针数组:
首先它是数组,成员是指针,使用它可以把不同位置不同长度的一维数组,组合构建出不规则的二维数组。
类型* 数组名[长度] = {地址1,地址2,...};
它有两种遍历方法:
for(int i=0; i<长度; i++)
{
for(int j=0; j<每行的列数; j++)
{
printf("%d ",*(*(数组名+i)+j));
printf("%d ",数组名[i][j]);
}
printf("\n");
}
在第三个printf中并没有改变cpp的值,依旧是指向cp[2],而cpp[-1][-1]+1可以表达成*(cpp-1)[-1]+1,进一步可以表达为*(*(cpp-1)-1)+1。因为cpp指向的使cp[2],所以*(cpp-1)=cp[1],而cp[1]是一个指向c[2]的指针,所以*(*(cpp-1)-1)指向的使c[1],所以cpp[-1][-1]+1=c[1]+1,此时的c[1]指向的是NEW,所以c[1]+1是指向NEW中的E的位置,输出为EW。