一、二维数组与指针
例一
下面的程序运行结果是什么:
#include <stdio.h>
int main()
{
int arr[3][2] = { (1,2),(3,4),(5,6) };
int* p = arr[0];
printf("%d\n", *p);
return 0;
}
运行结果:
实际上这里有个小细节,就是二维数组的初始化实际上是不完全初始换,因为,内部用的是小括号,所以这里的 (1,2) (3,4) (5,6) 都是逗号表达式,逗号表达式的结果是最后一个表达式的结果,所以这里的初始化相当于:
int arr[3][2] = { 2,4,6 };
所以具体的初始化情况:
arr[0] 就代表二位数组第一个子数组的第一个元素的地址,所以 *p 就能访问到二位数组第一个子数组的第一个元素的地址,即为 arr[0][0] ,所以运行结果是2。
例二
下面的程序在32位平台运行的结果是什么:
#include <stdio.h>
int main()
{
int arr[5][5];
int(*p)[4];
p = (int(*)[4])arr;
printf("%p\n%d\n", &p[4][2] - &arr[4][2], &p[4][2] - &arr[4][2]);
return 0;
}
运行结果:
下面我们来详细分析:
我们知道对于二维数组在内存中的存储依旧是连续的,就像一维数组那样,所以这里我们将其简化为这样(这里一个格子代表一个整型数据):
中间的空格为了方便看出每一个子数组。
在这一步:
int(*p)[4];
p = (int(*)[4])arr;
对于 p 是一个数组指针,类型是 int(*)[4] 然后对于 arr 就是二维数组首个子数组的地址,其类型是 int(*)[5] ,然后被强制转换为了 int(*)[4] 类型,然后赋给了 p ,假设这里的 p 中的数值是0x00ff1200:
但是由于 p 的类型是 int(*)[4] ,所以 p 一次是访问四个整型,对于 &p[4][2] 就相当于 &*(*(p + 4) + 2) ,前面的&与*可以相互抵消,所以还可以表示为 (*(p + 4) + 2) ,具体在图中就是:
然后对于 &arr[4][2] 就是二维数组第五行第三列的元素的地址,在途中就是这样:
我们可以看到这两个指针之间相差了四个整型数据,对于指针减指针,会返回指针之间的元素个数,较大的指针减较小的指针,会返回正数,反之,就返回负数,所以这里会返回-4。
这就可以解释第二个打印结果。
然而第一个结果为什么是0xFFFFFFFC呢?
这是因为%p是打印指针形式的数据,由于指针形式的数据都是非负的,就是打印十六进制非负数,而这里的-4又是负数,所以这里就被转换为正数了:
所以第一个打印结果是FFFFFFFC。
二、指针数组与指针
例一
下面的程序运行结果是什么:
#include <stdio.h>
int main()
{
const char* str[] = { "hello","world","abc" };
const char** p = str;
p++;
printf("%s\n", *p);
return 0;
}
运行结果:
对于str数组是字符指针数组,只不过这里的这里的字符指针是指向只读段的字符串的指针,然后及将数组的首元素地址付给 p ,然后 p 自增一,对于某种指针自增一,就会跳过这个指针指向的数据类型的大小,所以这里会跳过一个指针的大小,所以是指向数组第二个元素的指针,所以*p就是访问数组的第二个元素,第二个元素是指向 “world” 字符串的第一个字符 'w' 的指针,所以会打印 world 。
例二
下面的代码运行结果是什么:
#include <stdio.h>
int main()
{
const char* ps[] = { "hello","fgh","abcde","world"};
const char** pps[] = { ps + 3,ps + 2,ps + 1,ps };
const char*** ppps = pps;
printf("%s\n", **++ppps );//abcde
printf("%s\n", *-- * ++ppps + 3);//lo
printf("%s\n", *ppps[-2] + 3);//ld
printf("%s\n", ppps[-1][-1] + 1); //gh
return 0;
}
运行结果:
下面我们来具体分析:
具体的对应情况(这里的一个格子代表一个指针元素):
首先是第一句:
printf("%s\n", **++ppps );//abcde
++的优先级是大于*的,所以 ppps 先加加,最开始 ppps 指向 pps 的首元素,即 ppps 中是 pps 的首元素地址,然后加加后,就指向了 pps 的第二个元素,然后解引用就是 pps 的第二个元素 ps + 2,然后 ps + 2 就是 ps 的第三个元素的地址,所以 ps + 2 指向 ps 的第三个元素,然后解引用,就是 ps 的第三个元素,ps 的第三个元素是指向 "abcde" 这个字符串的第一个字符 'a' 的指针,所以会打印 abcde 这个字符串。
然后第二句:
printf("%s\n", *-- * ++ppps + 3);//lo
在上一句时因为加加,ppps 已经变成指向 pps 第二个元素的指针了,
然后这一句又有++,所以 ppps 指向的位置又向后面一了一个元素,所以这时 ppps 就指向 pps 的第三个元素了,
然后解引用,就访问到了 pps[2] ,然后就得到了 ps + 1 ,然后--,就变成了 ps ,这里的 pps[2] 中的 ps + 1 也变成了 ps ,
ps 是数组 ps 的首元素的地址,所以 ps 指向数组 ps 的第一个元素,然后解引用,得到数组 ps 的第一个元素,即 ps[0] ,ps 的第一个元素是指向字符串 "hello" 的指针,然后因为 + 加操作符优先级在这里是最低的,所以最后进行,这时是 "hello" 字符串中第一个字符 'h' 的指针,然后进行加三操作,实际上会跳过三个字符,下图中每个格子代表一个字符数据:
这时指针指向 "hello" 中的第二个 'l' ,所以这里会打印 lo 。
然后第三句:
printf("%s\n", *ppps[-2] + 3);//ld
对于 ppps ,其在第二句完成后,就指向了 pps 的第三个元素了,我们知道 ppps[-2] 就相当于 *(ppps - 2) ,但这个操作不会使 ppps 指向的内容发生改变,因为没有对 ppps 造成改动,所以进行这个操作后,就得到了 pps 的第一个元素 ps + 3,ps + 3 指向数组 ps 的第四个元素,然后再解引用,就得到了 ps 的第四个元素,也就是 ps[3] ,ps[3] 是一个字符指针,指向字符串 "world" 第一个字符 'w' 的指针,然后对其进行加三操作,就会得到指向 "world" 的第四个字符 'l' 的指针,
所以会打印 ld 。
最后第四句:
printf("%s\n", ppps[-1][-1] + 1); //gh
第三句中没有改变 ppps 指向的内容,所以 ppps 还是指向 pps 的第三个元素,然后进行 ppps[-1][-1] 操作,这个操作就相当于 *(*(ppps - 1) - 1) ,里面的 *(ppps - 1) 的进行后就得到了 pps 的第二个元素了,即 pps[1] ,就是 ps + 2 ,然后外层减一,就变成了 ps + 1 ,ps + 1 指向数组 ps 的第二个元素,解引用后就得到了数组 ps 的第二个元素了,即 ps[1] ,而ps[1] 是一个指向字符串 "fgh" 第一个字符 'f' 的指针,然后进行加一操作,就指向了 "fgh" 的第二个字符 'g' ,
所以会打印 gh 。