C语言——小知识和小细节15

一、二维数组与指针

例一

下面的程序运行结果是什么:

#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 。

  • 25
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值