攻破《数组与指针》相关笔试题(二)

目录

回顾:

关于指针:

题目一:

题目二:

题1.

题2.

题3.

题目三:

题目四:

题目五:

题目六:

题目七:

题目八:(含金量题)

语句一:

语句二:

语句三:

语句四:

总结:


回顾:

上一篇博客为“攻破《数组与指针》相关笔试题(一)”,在此blog中,我们对关于一维数组以及二维数组中各个经典笔试题题型进行了讲解以及补充。

我们主要利用的是三板斧来进行判断:

1.sizeof(数组名)------此数组名表示的是整个数组的地址。

2.&(数组名)-------此数组名表示的是取出整个数组的地址

3.sizeof(地址)------该结果永远都为4或8个字节,因为地址只占4或8个字节。

具体的细节内容请参考以下链接:

攻破《数组与指针》相关笔试题(一)_无双@的博客-CSDN博客

关于指针:

既然我们学习了指针,到现在我们可以尝试去解决面试中关于指针的面试题,那么我们一定要记住一句话。

“不要在门缝里看指针,把指针看扁了!”


题目一:

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 --首元素的地址。

ptr----取出数组整个的地址,并+1跳过了整个数组,所以指向数组最末端。

如图所示不难得出,答案为:

2,5

题目二:

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;
}

当我们看到该题目,我们就知道这是一道有关结构体的经典例题。

我们在这里不需要考虑结构体中的内存对齐,我们还是逐一分析。

我们先对p进行分析,

p在此处表示的是指向该结构体的结构体指针。

题1.

对于该代码来说,p + 0x1表示的就是结构体指针+1,即跳过一整个结构体,并以16进制的方式将地址打印出来。

那么就表示0x100000 + 20(十进制的20),换算成16进制答案为:

0x100014

题2.

我们在这里是将p强制类型转换为(unsigned long)既无符号长整型变量,既然是整形变量,+1就是进行的普通运算,直接相加即可。所以答案为:

0x100001

题3.

此时p被强制类型转换为(unsigned int*)即无符号整形变量,对于int该整型变量,我们进行+1操作,实际上就是p跳过一个int类型,即p跳过4个字节,所以答案为:

0x100004

输出结果为:

题目三:

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);
}

对于这道题来说,我们应当提前知道%x是什么含义,%x表示的是以16进制的方式进行打印。

1--------0x00000001

2--------0x00000002

我们对于ptr1指针的指向现阶段可以很好的判断出来,但对于ptr2就需要进行有效的讲解。

对于代码:(int*)((int)a + 1)

我们先分析内部,即(int)a + 1。

该代码表示的是将地址转换为整形,再+1,即仅仅跳过了一个字节。

那表示在数组首元素的下一个字节,又再括号前端加上了(int*)即将此时的a转换为一个指针,指向数组首元素的下一个字节。

我们在此进行文字说明较为抽象,可能你会感到疑惑,数组不是整形吗,整形与整形相差4个字节,那么+1跳过一个字节又是指向哪里呢?

对于你的疑惑我们可以利用画图来说明。

但首先,我们要理解大小端,我目前的机器为32位,以小端的方式进行存储。

那么该数组在内存布局就如图所示:

红色的区域就表示ptr2访问的数据,即表示0x02000000,所以*ptr2的答案就是

0x02000000

ptr[-1]输出结果就为:

0x00000004

输出结果为:

我们在此输出时,系统会将0x02000000中,2前面的0给省略掉。

题目四:

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

这道题是一道有坑有雷的题型,如果你对二维数组的基础知识不清楚的话,及其容易遇到坑并跳下去。

在这里要注意的是:

二维数组中的各个一维数组,用的是{}大花括号!而不是普通小括号!

所以图示是三个括号表达式,所以在内存中的存放应如图所示:

那么对于p = a[0]

又要打印p[0]。

我们在上一篇blog中就要专门对二维数组进行讲解,若是忘记了可以回看上文。

所以输出结果应该是第一行的第一个元素。

即答案为:
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;
}

在这里我们要知道我们要计算的是什么,我们在这里需要计算的是两个地址的差,即两个指针相减

在此我们还是一个二维数组,但是在这道题我们不能轻易去将a[5][5]理解为一个二维空间立体的例子,而是应该理解为内存真实的样子,在内存中连续存放。如图:

那么我们现在来分析a和p指针。

首先我们要了解它们的类型,因为a为二维数组所以不难得出:

a ----- int(*)[5]

p ----- int(*)[4]

要注意的是这里可能会存在报错“间接级别不同”,但是无伤大雅。

接下来两个指针的指向如图所示:

由图可知,两个指针中间相隔4个元素,所以按照%d来打印就为-4,而对于%p来说,

%p是按照16进制打印地址,一般认为内存中存储的补码就是地址。

所以对于-4转换为16进制:

所以最终的输出结果为:

题目六:

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;
}

对于这道题,ptr1相信大家已经轻车熟路了,可以一目了然知道ptr1指向的位置,那么对于ptr2所指向的位置可能还不是特别了解,在这里我们着重讲解ptr2

首先我们要知道ptr2可以进行一下转换:

*(aa + 1)  <=> aa[1] <=> &aa[1][0]

即第一行数组的首元素地址。

所以我们可以利用画图直观表达他们:

综上,该题的输出结果为:

10,5

题目七:

int main()
{
	char* a[] = { "work", "at", "alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

在我们拿到这道题时,我们要思考此时的a表示的是什么?

还应当注意的是pa++后的值是不发生变化的。

此时此刻a表示的是一个指针数组。

了解此处之后,我们可以进行画图操作,来进行较好的理解。

利用这张图可以充分的说明a与pa之间的关系。

在代码执行的过程中我们有进行pa++操作,即如图:

此时对pa进行解引用操作,就可以得到a指向的元素,即“at”。

所以答案为:

at

输出结果为:

该题为前几年的阿里巴巴面试题。

题目八:(含金量题)

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

	printf("%s\n", **++cpp);           //1
	printf("%s\n", *--*++cpp + 3);     //2   
	printf("%s\n", *cpp[-2] + 3);      //3
	printf("%s\n", cpp[-1][-1] + 1);   //4
	return 0;
}

该题目为本文最具代表性和最有含金量的一道题,以上的题目均是对这道题作为铺垫如果以上的题你觉得小菜一碟,不妨先尝试解决一下此题目,再看讲解。

还是一样,我们可以先进行画图操作:

以上为c,cp和cpp之间的关系

现在我们逐一分析

语句一:

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

这里我们需要知道的是,++的优先级比*高,所以我们是先进行对cpp进行前置++操作,所以就应如图所示:

如果我们只单单进行一次解引用操作,我们只能得到c + 2指向的地址,所以我们需要再进行一次解引用操作这样就可以直接找到“POINT”。

要注意的是,cp和c的类型都为指针数组。

所以该语句的输出结果为:

POINT

语句二:

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

因为我们在上述语句完成了一次前置++操作,那么在此cpp仍然指向c + 2。

在这里,我们又对cpp进行了一次++操作,那么这次我们的cpp就会指向如图所示:

再对于*++cpp来说呢,就会访问到c + 1这里,

但是我们在这里进行了前置--操作,也就是说c + 1会被改成 c

如图所示:

此时变为c之后,指向的就是“ENTER”了,且因为是前置--,所以值永久改变了。

再次解引用后,即*--*++cpp,访问的就是“ENTER”,指向的就是“ENTER”首元素的地址,

我们进行+ 3 操作得到的就是“ER”了。

所以该语句的输出结果为

ER

语句三:

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

对于此时的cpp来说,是指向第三个位置的。

而cpp[-2] <=>*(cpp - 2) 

所以cpp[-2]直接访问到c + 3,如图所示:

再次解引用后即*cpp[-2]可以直接访问到“FIRST”,并且指向“FIRST”首元素的地址,再进行+ 3操作后,访问的就是“ST”。

所以该语句的答案为:

“ST”

语句四:

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

我们首先要知道此时的cpp指向的位置,在上述语句中,我们仅仅是对cpp进行-2操作,所以仅仅是临时改变了cpp的指向,那么此时cpp仍然指向第三个元素,即如图所示:

对于cpp[-1][-1]

我们可以理解为*(*(cpp - 1) - 1),

那么照此看来,cpp应在如图所示的指向:

当第一次解引用访问到c+2时,又进行了-1操作,这样就是将c + 2改为了 c + 1,即如图所示:

这样就访问到了“NEW”,此时指向的就是“NEW”首元素的地址,当我们+1后,得到的就是“EW”。

故此语句的答案为:

“EW”

综上,该题目的输出结果为:

由此以来,上述八道题目全部讲解完毕。

总结:

以上内容和上一篇blog内容为数组与指针在面试中遇到的常见面试题,我们已带领大家逐一攻破,在接下来的时间我们可以重新阅读,并动手解决。

其中第八题的含金量尤为之高,下来可以对第八题进行多次练习,并且尝试给自己讲解,知道能自圆其说的讲解完毕。

该数组题目全部在我的Gitee仓库里,需要参考可以访问如下链接:

The_Pointer_interview_question_CSDN/The_Pointer_interview_question_CSDN/test.c · 无双/test_c_with_X1 - Gitee.comicon-default.png?t=N7T8https://gitee.com/wushuangqq/test_c_with_-x1/blob/master/The_Pointer_interview_question_CSDN/The_Pointer_interview_question_CSDN/test.c

记住下来一定要动手动手!!

“坐而言不如起而行”

“Action speak louder than words”


想看上一篇blog可以参考:

攻破《数组与指针》相关笔试题(一)_无双@的博客-CSDN博客

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无双@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值