前言:
本篇在了解指针和数组的关联后,利用已知道的知识去进行一些指针面试题的解析(数组篇在前面的文章哦),让大家更深入了解知识的背后是如何运用的。
数组篇面试题在这里:【C语言】从入门到入土(数组面试题的解析)
1.笔试题①
请写出下面代码输出结果:
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
答案: 2,5
解析:首先我们要理解ptr
这个指针存储的是什么,在这里&a
是取出整个数组的地址,然后&a+1
就是跳过一个数组的大小,在指向下一个数组(和数组a同大小)的首元素。而且&a+1
是int (*) [5]
类型的,前面的int *
将其强制类型转换,可以存进ptr
里。所以ptr
存的是下一个数组首元素的地址。
解答:所以当打印的时候,第一个*(a + 1)
实际上a是首元素地址,而a+1就是第二个元素的地址,将其*解引用得到的就是第二个元素,也就是2。第二个*(ptr - 1)
前面说到ptr存的就是下一个数组首元素地址,然后ptr-1就是回到a的末端也就是第五个元素的地址,解引用得到得到的就是第五个元素,也就是5。
![](https://img-blog.csdnimg.cn/a95b4713b5554c98867e4b9f9db727fb.png)
2.笔试题②
已知结构体的大小是20个字节(x86环境下),假设p 的值为
0x100000
。 如下表表达式的值分别为多少?
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p = (struct Test*)0X100000
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
答案:00100014 ,00100001 ,00100004
解析:这题实际上是考大家的类型大小,前面的结构体Test类型的变量大小已经给出来了,还有后面的整形,还有整形指针都是学过的变量大小。实际上在后面加上0x1就是加上了16进制的1,会跳过多少空间。
解答:在这里一开始的时候p是一个结构体指针,然后把0X100000
强制类型转换后赋给p,所以p就是地址为0X100000
的一个结构体指针。然后我们已知结构体大小是20字节,所以当p + 0x1
时就是加上一个结构体大小,所以得到的是0X100014
(16进制下20就是这里的14)。第二个p被强制类型转换成unsigned long
也就是长整形,那么这里的+1也就是纯+1,所以得到0X100001
。第三个是整形指针类型,在x86环境下指针大小为4字节,所以这里加上后就是0X100004
。
3.笔试题③
请写出下面代码输出结果:
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以十六进制数形式输出整数,或输出字符串的地址。
return 0;
}
答案: 4 , 2000000
解析:这题考的是指针加减整数的时候,跳过的空间大小和位置,也涉及到了一些大小端存储的知识,一步一步分析,把指针和类型都会了,这个题目也就迎刃而解了。
在这里我们先画图展示一下小端存储模式:
我们存进去的值是1 2 3 4,在存储中就表示为16进制的00 00 00 01等,然后在小端存储模式中,高位存储到高地址,低位存储到低地址。所以我们看到在存储中 00 00 00 01存储的时候是反过来存储的。
解答:
首先看ptr1
,&a大家都熟悉,是整个数组a的地址,&a+1就是跳过一个数组大小后指向的地址,也就是图示中指向的&a+1。然后强制类型转换为int *
整形指针,存的就是指向的地方的地址,所以当打印的时候ptr1[-1]
(化为*(ptr-1
))也就是往后退一个元素,得到的就是数组a中的4了,4打印出来还是4。
![](https://img-blog.csdnimg.cn/57bd8bfa495e4ef5965f61c7677bcca9.png)
然后我们看ptr2
,a表示的就是首元素地址也就是1的地址,假设首元素地址是0x0012ff40
,其实地址也是一个16进制的数字,这里强制类型转换int化成十进制就是1244992了,那+1不就是1244993吗,然后又强制类型转换回int *
,实际上就是原地址向前了一个字节变成0x0012ff41
。那既然向前了一个字节,打印的时候打印的就是下面图片这里的地址咯,得到的是00 00 00 02,还记得放进去的时候反过来了吗,所以打印的时候是02 00 00 00,前面的0可以省略掉。
![](https://img-blog.csdnimg.cn/77a85a273d684684927ad69cb9031fa8.png)
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;
}
答案: 1
解析:这一题实际上不难,但是是有坑的,我们看见的二维数组一看下去,以为刚好是三行二列的数据,实际上不是,这里每一个括号内都是一个逗号表达式,要分清楚运算。
解答:
在这里的坑就是创建二维数组的时候,括号内的逗号表达式。我们看到以为数组里面就是0 1 2 3 4 5了,实际上(0,1)
是逗号表达式,选择的是后面的数,所以数字里面实际上是只有{1,3,5}
。如果想这样表达应该是{{0,1},{2,3},{4,5}}
。然后创建指针p
存储a[0]
,这里a[0]
表示的是首元素地址,所以当打印的时候自然是打印1了。
5.面试题⑤
请写出下面代码输出结果:
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;
}
答案:FFFFFFFC , -4
解析:这一题就比较难一点了,是关于数组和指针还有一些数据类型转变之间的一些关联,具体的话需要画图理解才容易做出来,打印的时候也要注意打印的格式。
解答:首先我们先做图联系一下这题:
![](https://img-blog.csdnimg.cn/b0bd8151f91446fe99c5be6c9f3510dc.png)
首先我们先看题目让我们输出什么,&p[4][2]-&a[4][2]
就是这两个地方的差值。首先我们先看简单的&a[4][2]
,&a[4][2]
就是第四行第二个元素,就是下面这个地方的地址。
![](https://img-blog.csdnimg.cn/2d126e0e691f42c2b947f5ab45a8047a.png)
然后我们再看&p[4][2]
,我们创建了一个指针p
,然后将指向[4]
,所以是一个指针数组。然后p=a
。这里的a是二维数组数组名,表示首元素地址(第一行的地址就是a[0]的)。所以存储在p的地址就是a的首元素地址,就是图中p
所指的位置。然后我们分析一下&p[4][2]
是什么,我们学过,把p[4][2]
看成数组实际上就是*(*(p+4)+2)
,那p+4在图中我们也找到了,p+4里面的+2也就是第二个元素,就是&p[4][2]
了。
![](https://img-blog.csdnimg.cn/1fde7bfae90c4aa2817905969783a89c.png)
所以在这里我们的&p[4][2]
其实就是&a[3][2]
。然后我们进行运算,&p[4][2]-&a[4][2]
,我们之前说过,指针之间相减就是得到的是指针之间的元素个数,而且由于这里是低地址减高地址,所以在这里就是-4。但%p
和%d
打印可不一样。%d
打印的是数据的原码,而%p
打印的是地址,地址直接从内存中拿出来就是补码的值,而且全当作有效位。
![](https://img-blog.csdnimg.cn/4b8393d4604a45b8ae545d3fca9df271.png?)
所以这里地址是补码,而地址中的16进制,每4个1就是一个F,而最后是1100,也就是C,所以打印出来的地址就是FFFFFFFC
。
ps:这题在运行的时候肯定会报一些错误,因为p=a赋值的时候其实两者的类型不同。
6.面试题⑥
请写出下面代码输出结果:
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;
}
答案:10 , 5。
解析:这题实际上就是前面做过的题目结合了一点,也不算难,只要分析清楚数组名在每个地方表示什么,就可以完成这一题了。
解答:
我们先分析二维数组aa
,每一行5个元素,第一行是12345,第二行是678910。然后分析ptr1
,赋给ptr1
的是(int *)(&aa + 1)
,这里&aa
取出的就是整个数组的地址,所以+1跳过的整个数组,但是类型还是数组指针,强制类型转换后得到的就是二维数组下一个地方的地址,如图所示。然后打印的时候是ptr1-1
,得到的就是aa
最后的元素,也就是10。
![](https://img-blog.csdnimg.cn/6803136f1b7744ea999ededc6bb43964.png)
然后ptr2
中,aa
是首元素地址,也就是第一行的地址,+1跳到第二行上,然后再强制类型转换得到的就是第二行首元素的地址,然后ptr2-1
得到的就是第一行最后一个元素。也就是5。
![](https://img-blog.csdnimg.cn/a5e849cbfb2147e19076523a7d098c7c.png)
7.笔试题⑦(阿里巴巴曾经的题目)
请写出下面代码输出结果:
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
答案:at
解析:这一题实际上就是考指针数组和二级指针存储的时候,存储的是什么,然后当指针++的时候,跳过的是什么。
解答:当创建指针数组的时候,存储的情况如下图所示:
![](https://img-blog.csdnimg.cn/02c8d0f1e3ac4dcb8a9c16fee22c358d.png)
然后char ** pa = a
中,这里的a表示首元素地址,也就是指针数组中的第一个元素"work"
,所以pa中存储的是w的地址,然后通过w能找到整个字符串。然后进行pa++
,跳过一个指针大小,所以指向的就是第二个元素,也就是"at"
,然后因为存储的是字符串,所以本身就会在后面有\0,当打印完at之后不会再打印alibaba当其解引用得到的打印的字符串就是at
了。
![](https://img-blog.csdnimg.cn/2d80d934f84c4d4d89304fd8e3df4ca3.png)
8.笔试题⑧
请写出下面代码输出结果:
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;
}
答案:POINT ER ST EW
。
解析:这一题比较难,指针套娃,三级指针,然后打印中各种运算,但是重只要理清楚逻辑,每一个指针指向哪一个地方,一个一个的按顺序算下去,还是可以解决的。
解答:
首先我们先理一下创建c,cp,cpp
时的关系,c是指针数组,指向的依次是"ENTER","NEW","POINT","FIRST"
,c中的指针指向的是各字符串的首字母的地址。然后cp是二级指针数组,指向的分别是c+3,c+2,c+1,c
,也就是和c的存储刚好反过来。而cpp指向的是cp,也就是cp中首元素的地址,即为c+3的地址。
![](https://img-blog.csdnimg.cn/797ce30fe7cc4a8e9e3d815f4b091c52.png)
我们看第一个打印,**++cpp
,++cpp是先++后打印,所以cpp指向的地址要往前一个地址,也就是c+2
,然后第一个*
解引用,得到c+2处的内容,第二个*
再解引用,得到的就是char * c[2]
的内容。也就是指向字符串POINT
,所以打印的就是字符串POINT。
![](https://img-blog.csdnimg.cn/bfc9bea860dc46898f58c0599826e172.png)
然后是第二个打印*--*++cpp+3
,首先还是从cpp开始看起,刚刚第一个打印后cpp指向的变动是还在的,所以这里再++移动到的指向就是c+1
处的地址了。然后第一个*
解引用(靠近++的*
)得到的就是c+1处的内容。然后–就是地址–,所以就真的把刚刚指向的c+1处改为了c,也就是char** cp [2]
不再是指向c+1,而是指向c。然后第二个*
解引用得到的就是c的内容,c存储的是字符串首元素的地址也就是"ENTER"
中E的地址,然后现在后面+3,地址+3,c指向的就是"ENTER"
中第二个E的地址,所以打印的时候打印的就是"ER"
。(不打印后面的字符串是因为每个字符串后面都有\0,ENTER完后面也有\0结束打印。)
![](https://img-blog.csdnimg.cn/ba113d8260a644cf9fe6146acb273b85.png)
第三个*cpp[-2]+3
,我们可以先化为:*(*(cpp-2))+3
,这样更容易理解。首先是cpp-2,指向的是char ** cp[0]
也就是存储c+3地址的地方了。然后第一个*
解引用得到c+3处的地址,也就是字符串"FIRST"
中首元素F的地址,再+3后指向的就是S的地址,所以打印的时候打印出来的就是ST
了。
![](https://img-blog.csdnimg.cn/159c67e7b5ff4e079a089aaf5e39a21c.png)
最后一个 cpp[-1][-1]+1
也是同样的道理,先化一下:*(*(cpp-1)-1)+1
,这里我们先要清楚现在cpp的位置,第三个打印的时候只是可以理解成cpp-2
,实际上是并没有移动的噢。所以cpp还是指向char ** cp[2]
这个地方,然后cpp-1
指向的就是char ** cp[1]
的地方,解引用得到这里的内容也就是c+2的地址,然后再-1所以得到的就是c+1处地址的地址了,解引用得到c+1处的地址,也就是"NEW"首元素N的地址,最后+1跳过一个字节,指向的就是"NEW"中E的地址,最后打印就打印出了EW
了。
![](https://img-blog.csdnimg.cn/fab87ec927ae4e6caa061158bde9ac82.png)
好啦,本篇的内容就到这里,这就是指针笔试题篇的全部内容啦。最后也请继续关注我哦。关注一波,互相学习,共同进步。
还有一件事: