> 文档中心 > 【C语言】 - 指针进阶 - 经典笔试八题

【C语言】 - 指针进阶 - 经典笔试八题


目录

前言

 笔试题1 

 笔试题2

补充 

 笔试题3 

 笔试题4

 笔试题5 

 笔试题6

 笔试题7

 笔试题8 

 总结


前言

指针往往是变幻莫测,灵活多变,只有深入理解其本质才能有深刻的体会。

下面通过八道经典笔试题,来一起深入理解指针的神奇之处。 


 笔试题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;}

公布答案:

 为什么会这样呢?

 详细解释如下:

*(a + 1) - 解释:

就不再过多解释。

*(ptr - 1) - 解释·:

 &a表示取整个数组的地址,对其加一跳过整个数组,指针指向了红色箭头指向的位置,但是这个指针又被强制类型转换成了整形(int*)类型的指针,并存放在ptr这个整形指针变量里面去了。

此时的ptr指针就是一个整形指针,对其减一就是向前跳动一个整形的地址指向了蓝色箭头的位置,*(ptr - 1)是以整形指针的视角去读取所指空间的内容,也就是从蓝色箭头的指针处向后访问一个整形(4个字节)的长度,所以读到的就是5。


 笔试题2

 程序运行的结果是什么呢?

备注:

//p是结构体指针
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节

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

公布答案:

 为什么会这样呢?

 详细解释如下:

计算机内存十一字节划分的,一个字节都有一个对应的地址,地址是由16进制来表示的。

printf("%p\n", p + 0x1); - 解释:

p = (struct Test*)0x100000;

是将0x100000 强制类型转换成(struct Test*)这个结构体的地址。

而我们知道指针的类型决定了指针加减整数的时候跳过的字节数;

如:int*类型的指针 + 1 跳过4个字节,

      char* 类型的指针 + 1跳过1个字节,

      所以结构体类型的指针 + 1 就跳过一个结构体。

因为这个结构体的大小为20个字节,+1之后指针就向后跳20个字节,20的16进制是14 

所以结果就为00100014。

printf("%p\n", (unsigned long)p + 0x1); - 解释:

(unsigned long)p   是将这个结构体的地址强制类型转换成无符号整形

整形数字 + 1就是+1,所以结果是00100001。

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

(unsigned int*)p  是将p强制类型转换成整形指针,整形指针+1就是跳过四个字节,

所以结果是00100004。


补充 

大小端存储:

全称:大端字节序存储    小端字节序存储

大端字节序存储:

当一个数据的低字节的数据存放在高地址处,高字节的内容放在了低地址处

这样的存储方式就是大端字节序的存储。

小端字节序存储:

当一个数据的低字节的数据存放在低地址处,高字节的内容放在了高地址处

这样的存储方式就是小端字节序的存储。

写一个小程序来判断当前机器的字节序是大端还是小端:

int check_sys(){int a = 1;return *((char*)&a);//拿到的是低地址}int main(){int ret = check_sys();//返回1是小端,返回0是大端if (1 == ret){printf("小端\n");//低字节放在了低地址处}else if (0 == ret){printf("大端\n");//高字节放在了低地址处}return 0;}

临时变量存放在栈区,栈区使用地址的习惯是从高地址到低地址。

32位平台下,1的存储。

二进制:00000000000000000000000000000001

十六进制:00 00 00 01
     char* p = (char*)&a; - 将整形指针强制类型转换成字符指针。
    *p;访问第一个字节(低字节向高字节读取), 所以 *p 就拿到了低字节的存储数据。

小端存储示意图:

 大端存储示意图:


 笔试题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);return 0;}

公布答案:

 为什么会这样呢?

 详细解释如下:

注意:本题的输出方式为十六进制输出。

ptr1[-1] - 解释:

与笔试1类似,不再赘述。

*ptr2 - 解释:

    a是数组首元素的地址,是十六进制的,强制类型转换成整形(int)之后再 + 1
    再强转成整形指针,也就是在上几部操作后,a的地址数值加了1,地址加1
    就是向后偏移了一个字节。

在VS的环境下:存储方式为小端存储
    ptr从 01 指向了后一个字节 00。

 (int*)((int)a + 1) 再将其强转成整形指针,再向后访问四个字节并以十六进制的方式输出,

所以结果是:2 00 00 00。

以什么形式存储,就以什么形式输出,以小端存储方式存入,就以小端的方式输出。


 笔试题4

程序运行的结果是什么呢?

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

公布答案:

 为什么会这样呢?

 详细解释如下:

这道题很简单,逗号表达式是从左到右一次执行,并以最后一个值作为整个表达式的值。

所以,数组内部布局为:
    1   3 
    5   0
    0   0

所以p[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;}

公布答案:

  为什么会这样呢?

  详细解释如下:

p是一个数组指针,p能够指向的数组是4个元素,

a 为二维数组的数组名,二维数组的数组名表示二维数组第一行的地址。

同时二维数组可以理解为一位数组组成的数组。

p = a;

那么p + 1是向后跳了四个整形(int)16个字节。

详解图:

 a[4][2]的地址比p[4][2]的地址高4,所以结果为-4;

计算机存数据是以补码的形式存储:

-4的存储

100000000 00000000 00000000 00000100 - 原码

111111111 11111111 11111111 11111011 - 反码

111111111 11111111 11111111 11111100 - 补码

以%p的形式输出,就是以16进制的形式输出,结果为:FF FF FF FC。


 笔试题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;}

公布答案:

 为什么会这样呢?

 详细解释如下:

 与前面几题有所雷同,再次不再过多赘述。

只强调一点:&aa得到的是整个二维数组的地址,对其+ 1是跳过整个二维数组。

可以自己尝试解决。


 笔试题7

程序运行的结果是什么呢?

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

公布答案:

  为什么会这样呢?

  详细解释如下:

注意:

  形如  char* p = "abcdefg";这样的p为字符指针,存放的是字符串首元素的地址。同时它是      只 读 的 ,是不能被修改的。如果 *p = ‘w’;这样操作的话,编译器报警告。

(访问权限冲突)

a[]就是一个指针数组,里面存放的都是字符指针。

a是数组名是数组首元素的指针,char** pa;说明pa是个指针,这个指针指向的类型是个字符指针。

 pa++;就是相当于pa + 1,首元素向后跳动一个字符指针,便是"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);//*cp[1] -> *(c + 2) -> c[2]printf("%s\n", *-- * ++cpp + 3);printf("%s\n", *cpp[-2] + 3);//* 的优先级比 +的优先级第printf("%s\n", cpp[-1][-1] + 1);return 0;}

公布答案:

  为什么会这样呢?

  详细解释如下:

  一开始布局:

 c中放的是字符串首地址。

cp中放的是c中元素得地址。

cpp放的是cp首元素地址。

**++cpp - 解释:

补充:操作符的优先级:++ 高于 * 高于 + 高于 - 

先对cpp自增一次,此时cpp指向了(c + 2)的位置,一次解引用*操作就拿到了cp数组中的(c + 2),再次解引用*操作就拿到了c数组中第3个元素,第三个是个元素是字符指针,这个字符指针存放的是POINT这个字符串的首地址,%s拿到了POINT首地址向后打印,所以结果为POINT。

*-- * ++cpp + 3 - 解释:

cpp现在原只向的(c + 2)位置上自增1,此时指向了(c + 1)的位置上,解引用*一次便拿到了(c + 1),因为 -- 的优先级比+高,先执行 -- ,(c + 1)自减变成了c,再解引*用一次,就拿到c[]数组的第一个元素,第一个元素存的是ENTER的首地址,再对首地址 + 3,跳过三个字符,指向了E。%s拿到了E的地址向后打印,结果就是ER。

*cpp[-2] + 3 - 解释:

*cpp[-2] + 3    *(*(cpp - 2)) + 3,cpp在指向原来的(c + 1)位置,而(cpp - 2)向前跳动两个char**的元素指向了(c + 3)的位置,但是cpp本身的值没变,这时对(cpp - 2)解引用*一次就拿到了(c + 3),再对(c + 3)解引用*一次就拿到了c[]数组中的最后一个元素,c[]数组中的最后一个元素存的是FIRST字符串的首地址,对其+3就是跳过三个字符,指向了S。%s拿到了S的地址向后打印,结果就是ST。

cpp[-1][-1] + 1 - 解释:

cpp[-1][-1] + 1    *(*(cpp - 1) - 1) + 1,cpp在指向原来的(c + 1)位置,(cpp - 1)向前跳动一个char**的元素指向了(c + 2)的位置,但是cpp本身的值没变,这时对(cpp - 1)解引用*一次就

拿到了(c + 2),再对(c + 2) - 1得到了(c + 1),再对(c + 1)解引用*一次就拿到了c[]数组中的第二个元素,c[]数组中的第二个元素存的是NEW这个字符串的首地址,对其 +1就是跳过一个字符,指向了E。%s拿到了E的地址向后打印,结果就是EW。 


 总结

指针往往比较灵活且多变,空想很难有想出答案,最好的方法就是:画图。

结合题目的要求找到对应关系,当把所有的关系理清时,会有一种拨开迷雾见青山的快感。