玩透指针系列(终章)——指针笔试题(画图+超详解析)!

前言:本篇博客主要讲解关于指针的笔试题,其中涉及指针中的许多基础知识,如在完成笔试题时有不懂的知识点,详细请看:
玩透指针系列(一)
玩透指针系列(二)
玩透指针系列(三)
玩透指针系列(四)

指针笔试题

笔试题1

#include<stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?

画图分析:
在这里插入图片描述

因为 a 为数组,在这里为首元素地址,所以 a+1 就为 2 的地址;

int *ptr = (int *)(&a + 1);

解释:&a+1 为上图所示处的地址(假设存在的数组a后一个数组的地址),类型为数组指针,即int(*)[5],经强制类型转换后该指针就成为一个整型指针,指向数组a最后一个元素后的一个数据,所以 ptr-1 就为5的地址。
结果:
在这里插入图片描述

笔试题2

//由于还没学习结构体,这里告知结构体的大小是20个字节
#include<stdio.h>
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设 p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体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;
}
//程序的结果是什么?

代码解析:

printf("%p\n", p + 0x1);

解释:因为 p 的值为0x100000(地址),而 p 为结构体指针,结构体的大小为20字节,所以 p 每走一步的大小就为20字节,0x1(十六进制)=1(十进制),所以 p+0x1 == p+1 所以此时 p 移动了一步,所以p+1=0x100000+20==0x100000+0x00000014=0x100014,以%p形式打印就为 0x100014。

printf("%p\n", (unsigned long)p + 0x1);

解释:(unsigned long)p 为将 p 强制类型转换为一个整型,所以这里0x100000 不代表一个地址,而是一个 unsigned long 型的整数,此时 p 进行加减运算就以整数为单位进行运算,所以(unsigned long)p + 0x1 = 0x100001,所以输出的结果为 0x100001。

printf("%p\n", (unsigned int*)p + 0x1);

解释:(unsigned int*)p 即将p强制类型转换为 unsigned int* 型,所以p每走一步就移动一个unsigned int型数据的大小,即4字节,所以 (unsigned int)p + 0x1 == 0x100000+4 = 0x100004,最终输出结果就为 0x100004。

所以本题考察的是:指针类型决定了指针的运算(指针类型决定了指针步长)

笔试题3

#include<stdio.h>
int main()
{
int a[4] = { 1, 2, 3, 4 };`在这里插入代码片`
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
//程序的结果是什么?

画图分析:
在这里插入图片描述

规定该平台为小端存储模式,则数组a在内存中的存储情况如上图。

代码解析:

int *ptr1 = (int *)(&a + 1);

解释:&a+1 为a数组后一个数组(假设存在)的地址,如图,经强制类型转换后,ptr1就指向后一个数组的首元素(上图橙色箭头),基于指针ptr1的类型,所以 ptr-1 就指向的为数组a的最后一个元素(如图)。

int *ptr2 = (int *)((int)a + 1);

解释:(int)a 即将数组首元素的地址经强制类型转换为整型,所以 (int)a + 1 就为将首元素的地址的值+1 ,再将该值强制类型转换为 int* 型的指针,所以 ptr2与 a 的地址相差1字节,所以指针ptr2指向的内容如上图。

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

解释:
ptr[-1] == *(ptr-1), 因为 ptr-1 指向的内容在内存中的存储序列为:04 00 00 00,而规定的平台存储模式为小端存储,所以若以 %x 的形式打印,则实际的序列为:00 00 00 04 ,所以打印的结果为4(4前的0打印时省略)。
因为指针 ptr2 指向的内容在内存中的存储序列为:00 00 00 02,而 *ptr2 也以 %x 的形式打印,所以同理打印出的结果为:2000000 。

所以本题的结果为:4,2000000
在这里插入图片描述

笔试题4

#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}
//程序的结果是什么?

画图分析:
在这里插入图片描述
a[0]表示二维数组中第一行元素的数组名,此时没有sizeof,也没有取地址,所以a[0] 在这里表示第一行元素最后那个首元素的地址,即 1 的地址,所以指针 p 指向元素1,而 p[0] == *(p+0) = 1,所以程序运行打印的结果为 1 。

笔试题5

#include<stdio.h>
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;
}
//程序的结果是什么?

画图分析:
在这里插入图片描述
其中每个矩形块代表一个数组元素的空间。

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

因为a为二维数组,通过画图我们可知元素 a[4][2] 的位置如图(橙色矩形块),&a[4][2]即取出该位置的地址;因为p为一个数组指针,该指针的类型为 int(*)[4] ,所以该指针指向一个数组,数组四个元素,每个元素 int 型,而 p = a ,所以该指针指向二维数组的首元素,基于该指针的类型(认为p指针每走一步会跳过四个整型数据的大小),我们很容易知道&p[4][2] 所指向的位置(红色矩形块),所以由图知道 &p[4][2] - &a[4][2]= - 4(指针 - 指针 = 俩指针间元素的个数)。

注意:-4在内存中以补码的形式存储,以%p形式打印时,系统会将-4的补码认为是地址而打印出来,所以此时的结果为:FFFFFFFC
运行结果:
在这里插入图片描述

笔试题6

#include<stdio.h>
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", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//程序的结果是什么?

画图分析:
在这里插入图片描述
代码解析:

int *ptr1 = (int *)(&aa + 1);

解释:&aa+1 为跳过整个二维数组aa,而指向下一个二维数组的地址(假设存在),经过强制类型转换为int* 型指针后该地址就为aa数组后一个数组的首元素地址,所以 ptr1-1 指向aa的最后一个元素10,即 *(ptr-1) = 10 。

int *ptr2 = (int *)(*(aa + 1));

解释:此时的aa为数组名,即代表二维数组首元素第一行的地址,所以 aa+1 指向数组的第二行,如图所示,该地址解引用后就得到第二行数组名,而第二行数组名表示第二行首元素地址,指向整数6 (因为在未强制类型转换前就为整型指针了所这里的强制类型转换是多余的,别被迷惑了),所以ptr2-1 就指向 5 ,解引用后就得到5 。
在这里插入图片描述

笔试题7

#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//程序的结果是什么?

画图分析:
在这里插入图片描述
解释:a为一个指针数组,该数组三个元素,每个元素都为 char* 型指针,由数组的定义可知每个指针都指向一个字符串(指向每个字符串的首字符),因为 *char**pa = a;*此时的a为数组名,即首元素的地址,所以二级指针pa就指向数组的首元素一级指针,当 pa++ 后,基于指针的类型,该二级指针就变为指向了数组的第二个元素,解引用后就得到第二个元素,而第二个元素为一级指针,该指针指向字符串 “at” 的首字符,所以当以 %s 打印时,输出的结果就为 “at” 。
在这里插入图片描述

笔试题8(压轴)

#include<stdio.h>
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;
}
//程序的结果是什么?

画图分析:
由题目代码可知
指针数组 c 有4个元素,每个元素为 char* 型指针,每个指针均指向一个字符串。
二级指针数组 cp 中有4个元素,每个元素为 char** 型,每个元素均指向一个一级指针。
三级指针有 char*cpp = cp 而cp为二级指针数组,此时的cp为数组名,即为数组首元素地址,所以此时的三级指针 cpp 指向二级指针数组cp的首元素。

综上分析,可得各级指针指向的情况如下图所示:

在这里插入图片描述
代码解析:

printf("%s\n", **++cpp);

因为三级指针cpp指向二级指针数组 cp 的首元素,根据操作符优先级对表达示进行转换:
在这里插入图片描述
++cpp 后此时的 cpp 就指向了数组 cp 的第二个元素,第一次解引用后得到第二个元素的内容:即二级指针,指向一个一级指针;第二次解引用后得到这个一级指针 : 该指针指向字符串 “POINT”,即为首字符‘P’的地址(如下图蓝色箭头指向),当以 %s 打印后,输出的结果就为:POINT

为了加深理解,图解如下:
在这里插入图片描述

printf("%s\n", *--*++cpp+3);

解释:基于操作符的优先级,将该表达式进行转换:
在这里插入图片描述
经过上一行代码的运作,cpp 已经指向了数组 cp 的第二个元素(如下图蓝色箭头指向),所此时再经过 ++cpp 后,cpp 就指向了数组 cp 的第三个元素,所以 *(++cpp) 得到第三元素,第三元素为为二级指针,指向一级指针数组的第二个元素,再经过前置’ - - ’ 后此时的二级指针就指向了数组 c 的第一个元素(如下图橙色箭头指向),再次解引用后就得到数组 c 的第一个元素,即为字符串 “ENTER” 的首字符地址即字符 ‘E’ 的地址, ‘E’ 的地址+3 = ‘E’的地址,此时在以%s的形式打印出的结果就为:ER

图解如下:
在这里插入图片描述

printf("%s\n", *cpp[-2]+3);

解释:上述表达式进行转换:
在这里插入图片描述
经上一行代码运行 cpp 就指向了数组 cp 的第三个元素,所以这里的 *(cpp-2) 就得到了数组cp的第一个元素,即一个二级指针,该指针指向数组 c 的第四个元素;再次解引用后就得到数组 c 中的第四个元素(如下图紫色箭头所示),即为字符串 “FIRST” 的首字符地址,‘F’ 的地址+3 = ‘S’ 的地址,所以最后打印出的结果为:ST

图解如下:
在这里插入图片描述

printf("%s\n", cpp[-1][-1]+1);

将表达式进行转换:
在这里插入图片描述
因为经过两次前置‘ + + ’ 运算后 cpp 指向了数组 cp 的第三个元素,由表达式 cpp-1 可知cpp向后走动一步,指向了数组 cp 的第二个元素,解引用后就得到第二个元素:c+2 ,所以表达式 *(cpp-1)-1 = c+2-1 = c+1, 而c为指针数组的数组名,即代表数组首元素地址,所以 c+1 为数组第二个元素地址(如下图绿色箭头),解引用后得到第二个元素:字符串"NEW"的首字符地址,而 ‘N’的地址+1 = ‘E’的地址,所以最后打印的结果为:EW

图解如下:
在这里插入图片描述
运行结果为:
在这里插入图片描述

最后,相信小伙伴认真完成这些笔试题后一定会对指针的理解更加透彻!

  • 35
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值