【C语言进阶(5)】指针笔试题(带图分析)

  • 分析下面代码的结果为何是这样

笔试题 1

1. 笔试代码

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);

	printf("%d,%d\n", *(a + 1), *(ptr - 1));

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  1. *(a + 1):*(a + 1) == a[1],所以结果是 2。
  2. *(ptr - 1):取出整个数组 a 的地址,数组的地址 + 1 会跳过整个数组。然后再将地址赋给 ptr,此时 ptr 实际指向的是 5 后面那个位置;让 ptr - 1 此时 ptr 指向的就是 5

在这里插入图片描述

笔试题 2

1. 笔试代码

  • 此处结构体的大小在 32 位环境底下为 20 个字节。
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x000000。 如下表表达式的值分别为多少?
//已知,结构体 Test 类型的变量大小是20个字节
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  1. p + 0x1:p 是 struct Test 类型的指针,步长为 20,指针 + 1 会向后走 20 个字节,0x 14 就是 十进制的 20。
  2. (unsigned long)p + 0x1:将 p 变成了一个 unsigned long 类型的变量,也就是说 p 里面存的地址变成了 1 个十进制的数 00000000,将这个数 + 1 后转换成十进制结果就是 00000001。
  3. (unsigned int*)p + 0x1:将 p 强转为 unsigned int* 类型的指针,使得 p + 1 向后走一步的步长变为了 4 个字节,所以结果才会是 00000004。

笔试题 3

1. 笔试代码

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);

	printf("%x,%x\n", ptr1[-1], *ptr2);

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  1. ptr1[-1]:ptr1 == (int*)(&a + 1) 和第一题一样,指向的是 4 的后面,ptr1[-1] 等于 *(ptr1 - 1),此时 ptr 指向了 4,所以打印结果为 4。

在这里插入图片描述

  1. *ptr2:此时的 a 表示的是首元素的地址,将首元素地址强转为 int 类型。此时 a 面存着的就是个整型值,整型值 + 1 那就是 + 1 了。将 + 1 后的整型值强转为 int* 赋给 ptr2,此时 ptr 就指向第一个元素的第二个字节的内容。最后以打印 *ptr2 的结果,又因为 ptr2 是个 int* 类型的指针,所以*ptr2 就是从第一个元素的 2 个字节开始向后访问 4 个字节的内容。

在这里插入图片描述

笔试题 4

1. 笔试代码

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];

	printf("%d\n", p[0]);

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  • 这是一个三行二列的二维数组,然而要注意数组的初始化内放的其实是 3 个逗号表达式;
  • p = a[0] 将第一行的地址赋给 p,p[0] 就是首行首列也就是第一个表达式的内容 1 啦。

在这里插入图片描述

笔试题 5

1. 笔试代码

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;

	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  • 这题的二维数组如果画成多行多列的方式就不好讲了,所以直接按照二维数组在内存中存放的本质来画图。

在这里插入图片描述

  • p 是一个数组指针,它所指向的数组应该有 4 个元素,每个元素都是 int 类型,p + 1 会跳过 4 个int 类型的数据
  • a 作为二维数组数组名,表示首元素地址,它的类型应该是 int (*)[5],然而 p 的类型却为 int(*) [5]。
    • 在将 第一行的地址赋给 p 时 p 肯定时不能这么存的,但如果硬是要存的话,p 最终还是会指向 a[0][0] 的位置。
    • p 虽然指向了 a[0][0] 的位置,但是 p 认为它指向的数组只有 4 个整型,对 p 解引用 或 p + 1 的时候只会向后访问 4 个整型

在这里插入图片描述

3.1 &p[4][2] - &a[4][2]

  • a[4][2] 很好找,p[4][2] 可以转换成 ((p + 4) + 2),接下来就是分析 p + 4 以及 *(p + 4) + 2 在哪了。
    • p + 4:对于 p 来说,它所指向的数组应该只有 4 个元素,所以 p + 4 跳过的元素应该是 4 * 4 = 16,最后 p + 4 就指向了 a[3][1] 的为位置.
    • *(p + 4) + 2:*(p + 4) 就是访问从 a[3][1] 开始的向后 4 个元素,所以 *(p + 4) +2 的位置实际指向 a[3][3]

在这里插入图片描述

  • 两个指针(地址)相减的绝对值是两个地址之间的元素个数

  • 用小的地址(&p[4][2])减去大的地址(&a[4][2])的结果是 -4,但是以 %p 的形式打印 -4 出来就得先分析数据在内存中存储的情况了。

11111111 11111111 11111111 11111100  //-4 的补码-4 的补码以 %p 的形式打印时,&p 认为 -4 的补码是个地址,地址是没有原反补概念的。
所以 %p 会直接将 -4 的补码直接以 16 进制的形式打印出来,结果就是 FFFFFFFC

3.2 &p[4][2] - &a[4][2]

  • &p[4][2] 与 &a[4][2] 之间隔着 4 个元素,所以 &p[4][2] - &a[4][2] 结果就是 -4。以 %d 打印的出来的结果自然就是 -4 了。

笔试题 6

1. 笔试代码

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));

	printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1));

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  • *(ptr1 - 1):&aa 表示整个二维数组的地址,&aa + 1 指向了 10 的后面,ptr1 存着 10 后面的地址。将这个地址强转为 int* 赋给 ptr1,ptr1 的步长就变为了 4 个字节, ptr1 - 1 往前移动 4 个字节指向了 10 这个位置。

在这里插入图片描述

  • *(ptr2 - 1):aa 表示的是首元素(第一行)的地址,aa + 1 跳过第一行指向了 第二行的地址。*(aa + 1) 相当于直接拿到了第二行,也相当于拿到了第二行的数组名。而将第二行的数组名赋给 ptr2 ,ptr2 就等于指向了 第二行的第一个元素 6。因为 ptr2 是 int* 类型的指针,ptr2 - 1 往前移动 4 个字节指向了 5 这个元素。

在这里插入图片描述

笔试题7

1. 笔试代码

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;

	printf("%s\n", *pa);

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  • a 是一个字符指针数组,该数组存储着每个串的第一个字符的地址。
  • 数组名 a 表示数组首元素(“word”)的第一个字符 ‘w’ 的地址,将 a 赋给 pa 相当于让 pa 指向了数组的 ’ w '。

在这里插入图片描述

  • pa++ 相当于让 pa 跳过了一个 char* 的元素指向了 a[1],对 pa 解引用 通过 a[1] 的地址就找到了 a[1] 中存储的内容(“at”)了。

在这里插入图片描述

笔试题 8

1. 笔试代码

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;

	printf("%s\n", **++cpp);			//解析在 3.1
	printf("%s\n", *-- * ++cpp + 3);	//解析在 3.2
	printf("%s\n", *cpp[-2] + 3);		//解析在 3.3
	printf("%s\n", cpp[-1][-1] + 1);	//解析在 3.4

	return 0;
}

2. 代码答案

在这里插入图片描述

3. 代码分析

  • 整体布局图大致如下,具体怎么画的就不详细介绍了。

在这里插入图片描述

3.1:**++cpp

  1. ++cpp 指向了 cp[1],注意是对 cpp 进行 ++ 操作,此时 cpp 被改变彻底指向 cp[1]
  2. 第一次解引用(*cpp)后通过 cp[1] 中存储着的 c + 2 的地址找到了 c[2]。
  3. 第二次解引用(**cpp)后通过 c[2] 中存储着的 ’ P ’ 的地址找到了 ’ P ’ 这个字符。
  4. 最后再以 %s 的形式从 ’ p ’ 开始打印字符串 “POINT”。

在这里插入图片描述

3.2:*-- * ++cpp + 3

  • 注意:此时的 cpp 指向 cp[1] 的位置
  1. 先算 ++cpp 让 cpp 指向 cp[2] 的位置,此时 cpp 被彻底改为指向 cp[2]
  2. 对 ++cpp 后的结果解引用找到了 cp[2] 中存放着的 c + 1 这个值。
  3. 找到了 c + 1 ,让 c + 1 自减变成了 c (c[0])。c[0] 中存放着 “ENTRE” 的首字符 ’ E ’ 的地址。此时 cp[2] 中的值 c + 1 彻底被改成了 c
  4. 从首字符 ’ E ’ 的地址 + 3 指向了 " ENTER " 的第二个 ’ E '。
  5. 最后再解引用然后 %s 形式打印从 ’ E ’ 开始的字符串 “ER”。

在这里插入图片描述

3.3:*cpp[-2] + 3

  • 注意:此时的 cpp 指向了 cp[2] 的位置
  • *cpp[-2] + 3 等于 *(*(cpp - 2) + 3)
  1. cpp[-2] 等价于 *(cpp - 2),cpp - 2 指向了 cp[0] 的位置,*(cpp -2) 即访问 cp[0] 中存放的 c + 3 的地址。此时并没有改变 cpp 本身的值,cpp 本身还是指向 cp[2]
  2. *cpp[-2] :为访问 c + 3 中存放着的 “FIRST” 的首字符 ’ F ’ 的地址;
  3. *cpp[-2] + 3:为从 ’ F ’ 的地址开始指针 + 3 指向了 ’ S ’ 的地址。
  4. 最后以 %s 的形式打印从 ’ S ’ 开始的字符串 “ST”。

在这里插入图片描述

3.4:cpp[-1][-1] + 1

  • 注意:此时的 cpp 还是指向 cp[2] 的位置
  • cpp[-1][-1] + 1 等于 *(*(cpp - 1) - 1) + 1
  1. cpp[-1] 等价于 *(cpp - 1) ,cpp - 1 指向 cp[1] 这块空间。
  2. cpp[-1][-1] 等于 (cpp - 1) - 1,(cpp - 1) 的结果是 c + 2 这个数值,*(cpp - 1) - 1 是让 c + 2 这个数值变成 c + 1,然后再对这个值进行解引用,此时得到的值为 “NEW” 的首字符 ‘N’ 的地址。
  3. 从 ‘N’ 的地址 + 1,指向 ‘E’ 的地址。
  4. 最后以 &s 的形式打印从 ‘E’ 开始的字符串 “EW”。

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值