【C语言】从入门到入土(指针笔试题的解析)

前言:
本篇在了解指针和数组的关联后,利用已知道的知识去进行一些指针面试题的解析(数组篇在前面的文章哦),让大家更深入了解知识的背后是如何运用的。

数组篇面试题在这里:【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+1int (*) [5]类型的,前面的int * 将其强制类型转换,可以存进ptr里。所以ptr存的是下一个数组首元素的地址。

解答:所以当打印的时候,第一个*(a + 1)实际上a是首元素地址,而a+1就是第二个元素的地址,将其*解引用得到的就是第二个元素,也就是2。第二个*(ptr - 1)前面说到ptr存的就是下一个数组首元素地址,然后ptr-1就是回到a的末端也就是第五个元素的地址,解引用得到得到的就是第五个元素,也就是5。


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。

然后我们看ptr2,a表示的就是首元素地址也就是1的地址,假设首元素地址是0x0012ff40,其实地址也是一个16进制的数字,这里强制类型转换int化成十进制就是1244992了,那+1不就是1244993吗,然后又强制类型转换回int *,实际上就是原地址向前了一个字节变成0x0012ff41。那既然向前了一个字节,打印的时候打印的就是下面图片这里的地址咯,得到的是00 00 00 02,还记得放进去的时候反过来了吗,所以打印的时候是02 00 00 00,前面的0可以省略掉。


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

解析:这一题就比较难一点了,是关于数组和指针还有一些数据类型转变之间的一些关联,具体的话需要画图理解才容易做出来,打印的时候也要注意打印的格式。

解答:首先我们先做图联系一下这题:

首先我们先看题目让我们输出什么,&p[4][2]-&a[4][2]就是这两个地方的差值。首先我们先看简单的&a[4][2]&a[4][2]就是第四行第二个元素,就是下面这个地方的地址。

然后我们再看&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]了。

所以在这里我们的&p[4][2]其实就是&a[3][2]。然后我们进行运算,&p[4][2]-&a[4][2],我们之前说过,指针之间相减就是得到的是指针之间的元素个数,而且由于这里是低地址减高地址,所以在这里就是-4。但%p%d打印可不一样。%d打印的是数据的原码,而%p打印的是地址,地址直接从内存中拿出来就是补码的值,而且全当作有效位。

所以这里地址是补码,而地址中的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。

然后ptr2中,aa是首元素地址,也就是第一行的地址,+1跳到第二行上,然后再强制类型转换得到的就是第二行首元素的地址,然后ptr2-1得到的就是第一行最后一个元素。也就是5。


7.笔试题⑦(阿里巴巴曾经的题目)

请写出下面代码输出结果:

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

答案:at

解析:这一题实际上就是考指针数组和二级指针存储的时候,存储的是什么,然后当指针++的时候,跳过的是什么。

解答:当创建指针数组的时候,存储的情况如下图所示:

然后char ** pa = a中,这里的a表示首元素地址,也就是指针数组中的第一个元素"work",所以pa中存储的是w的地址,然后通过w能找到整个字符串。然后进行pa++,跳过一个指针大小,所以指向的就是第二个元素,也就是"at",然后因为存储的是字符串,所以本身就会在后面有\0,当打印完at之后不会再打印alibaba当其解引用得到的打印的字符串就是at了。


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

我们看第一个打印,**++cpp,++cpp是先++后打印,所以cpp指向的地址要往前一个地址,也就是c+2,然后第一个*解引用,得到c+2处的内容,第二个*再解引用,得到的就是char * c[2]的内容。也就是指向字符串POINT,所以打印的就是字符串POINT。

然后是第二个打印*--*++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结束打印。)

第三个*cpp[-2]+3,我们可以先化为:*(*(cpp-2))+3,这样更容易理解。首先是cpp-2,指向的是char ** cp[0]也就是存储c+3地址的地方了。然后第一个*解引用得到c+3处的地址,也就是字符串"FIRST"中首元素F的地址,再+3后指向的就是S的地址,所以打印的时候打印出来的就是ST了。

最后一个 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了。


好啦,本篇的内容就到这里,这就是指针笔试题篇的全部内容啦。最后也请继续关注我哦。关注一波,互相学习,共同进步。

还有一件事:

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

恒等于C

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

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

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

打赏作者

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

抵扣说明:

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

余额充值