C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)

不多说什么,直接上题目,带你掌握指针中的精髓题目。助你对指针有进一步的理解。
画图是对指针理解最重要的一步,不要以为编程就是敲代码,敲代码只是体力活,思考的过程才是关键。
注意:测试平台为vs2013,32位条件下,指针的大小位 4个字节。

文章目录

  • 第一题
  • 第二题
  • 第三题
  • 第四题
  • 第五题
  • 第六题
  • 第七题
  • 第八题
  • 总结

第一题

请问下面输出的结果是什么?

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;

开始分析:

int main()
{
  int a[5] = { 1, 2, 3, 4, 5 };
  首先这里有一个数组,数组有五个元素;每个元素的类型为int类型,每个元素占 4 个字节;
  
  int *ptr = (int *)(&a + 1);
  这里 &a,表示取出整个数组的地址,该地址的类型为:int(*)[5],&a + 1,
  在这里表示,跳过了 1 数组的大小个字节,即指向了下面图的位置:

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第1张图片

  int *ptr = (int *)(&a + 1);
  然后 &a + 1转化为 int* 的指针类型,交给 ptr指针维护,
  即 ptr指针,指向了 &a + 1的位置;
  
  printf( "%d,%d", *(a + 1), *(ptr - 1));
  在这里 a 表示数组首元素的地址, a+1表示跳过了一个元素类型的地址,即是(a+1) == &a[1];
  当对 a+1解引用,*(a+1),表示 a[1]的元素,以 %d的形式打印,结果为:2;
  而 指针 ptr的类型为 int*,类型决定指针访问的字节,
  所以该 ptr - 1,表示指针ptr 向前访问了4个字节,即对其解引用 *(ptr - 1)
  表示 a[5]元素,结果为:5; 
  图解如下:
  

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第2张图片


走一波程序执行结果看看。
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第3张图片


第二题

输出结果是什么:

//假设结构体的大小位 20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}* p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
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;
}
//最终结果:0x100014;0x100001;0x100004; 

开始分析:

p = (struct Test*)0x100000;
让p指向该地址0x100000;

printf("%p\n", p + 0x1);
首先,指针p是指向结构体 struct Test 的指针,
其次我们要清楚的明白,对于指针的加减整数,就是要清楚的知道,指针的类型是什么,
因为指针的类型,决定了我指针加减整数时候,跳过的字节个数。
在 这里 p + 0x1,并不是 0x100000 + 0x1 那么简单;(0x表示十六进制);
这是指针 加 整数,所以 p+ 1 = 0x100014;p+1跳过了20个字节,由于是十六进制,
所以 p+1 == 0x100014; 即该结果为:0x100014;

printf("%p\n", (unsigned long)p + 0x1);
在这里, 指针 p 转换为 unsigned long 类型,是一个整数, 整数 + 1就是一个加法运算,
即 (unsigned long)p + 0x1 = 0x100000 + 0x1 = 0x100001;
但是,这里打印 以 %p形式打印,结果就是 0x100001.

printf("%p\n", (unsigned int*)p + 0x1);
在这里,指针 p 转化为 unsigned int*类型的指针,有什么用呢? 作用就是决定了 
指针p加减整数访问字节的个数,由于是unsigned int* ,所以 p + 0x1,表示 p 指针跳过了 4个字节;
即 p + 0x1 = 0x100004; 所以最终结果为 0x100004

我们也是 上机测试一下:
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第4张图片


第三题

输出结果是什么:

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

分析过程:

  int a[4] = { 1, 2, 3, 4 };
  首先:这里是一个数组名 a ,有四个元素,每个元素的类型为int ,每个元素的字节大小为 4 ,
  整个数组的字节大小为 16int *ptr1 = (int *)(&a + 1);
  &a + 1,就是数组 a 跳过了 1个数组大小的字节,指向了数组最后一个元素的下一个位置,
  (int*)(&a + 1) ,类型转化为 int*, 目的很明确,就是为了访问字节的个数 为 4个,
  交过 ptr1 指针维护;即 ptr1 指向了数组a 最后一个元素的下一个位置。
  
  int *ptr2 = (int *)((int)a + 1);
  在这里 把数组 a,转化为 int 类型 ,(int)a就表示整数了,(int)a + 1,表示该整数 + 1,
  然后又转化为指针int*类型,(int*)((int)a + 1),
  表示 该指针(int*)((int)a + 1)指向了比a的地址还多1个字节的地址。
  看看内存布局如下图:(为了方便说明,我们把每个数组元素的每个字节都画出来了,vs2013是小端模式)

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第5张图片


  printf( "%x,%x", ptr1[-1], *ptr2);
  来到这里 ptr1[-1],通过指针 ptr1去和中括号[ ] 一起使用,表示的是 ptr[-1] = *(ptr - 1);
  即我们只需要找到 ptr - 1的位置,解引用就可以得到 ptr[-1]的值了,在这里 ptr -1 ,表示
  跳过了一个 数组元素,向前移动了 4 个字节,这个移动字节的个数,是 ptr的指针类型 int* 决定的。
  所以呢 ptr[-1] 在这里表示 a[3] == 4;
  但是,这里以%x,打印的是十六进制的形式,所以最终结果为:0x4;
  对于, *ptr2 ,表示解引用指针ptr2地址对应的内容,但是对指针解引用的前提,我们要清楚指针的
  类型,这里指针 ptr2的类型为 int*, 所以解引用指针访问4个字节,在这里 *ptr2,我们从图看,
  从 ptr2的位置读取四个字节:00 00 00 02 ,由于是小端存储模式,所以读取数据时候,看到的为:
  02 00 00 00;以 % x的形式打印,即该值最终结果为:0x2 00 00 00;

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第6张图片


我们还是上机测试一下吧:
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第7张图片


第四题

结果是什么:

#include 
int main()
{
  int a[3][2] = { (0, 1), (2, 3), (4, 5) };
  int *p;
  p = a[0];
  printf( "%d", p[0]);
return 0;
}
//最终结果:1 

分析过程:

这一题就相对来说很简单了
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
首先我们要明白逗号表达式的取值为括号最后一个值,
所以这里表达的意思是初始化,内存布局如下:

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第8张图片

  int *p;
  p = a[0];
  这里指针 p 指向 a[0],a [0] 表示 *(a + 0);即二维数组的首元素地址;
  把二维数组看成一维数组组成的话,那么 a[0] 就表示指向一维数组的指针,即p指向该位置,
  为指向一维数组的指针;
  printf( "%d", p[0]);
  所以 p [0] = *(p + 0);表示该指针指向的二维数组的第一行的首元素的值,即 p[0] == 1;
  最终结果为 1

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第9张图片


第五题

输出结果:
这道题有点难,不过如果懂原理,一步步分析也是可以的,

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;
}
//最终结果:0xff ff ff fc 和 -4;

分析过程:
首先我们得明白二维数组得本质就是一维数组;
其次我们要知道int(*p)[4]的指针类型,其次要知道指针类型代表有什么意义;
指针加减整数代表什么,指针(地址相减代表什么);
如果上面的原理都懂,这道题就是照猫画虎的,很简单;

int a[5][5];
二维数组本质就是一维数组,在内存布局时连续的,

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第10张图片

int(*p)[4];
p是一个数组指针,该指针类型为 int(*)p[4],代表指针p所能访问的字节为:指针p所指向的数组,
该数组有4个元素,每个元素类型为int,数组共16个字节;

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第11张图片

  p = a;
  这句话的意思是 指针 p 指向 了数组 a,
  要注意, 指针 p的类型 为 int(*)[4],只能一次访问 16个字节;
  而二维数组 a,是一个由5个一维数组组成的数组;
  
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

	&p[4][2],通过指针 p 和下标访问数组,&p[4][2] == *(*(p + 4) +2);
	&a[4][2],是二维数组的访问,a[4][2] == *(a + 4) + 2;

在内存布局如下:

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第12张图片
这时候我们就很清楚啦!

&p[4][2] - &a[4][2] 
表达的意思是地址 - 地址,表示他们之间相差的距离,所以该值为 - 4,因为 &p[4][2]的地址小于&a[4][2]printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    在看这里,是一个以 %p打印的,和%d打印的,%p打印的就是地址,地址是不可能由负数的,所以这里 -4%p打印,会出现 一个 -4转化为 无符号类型的打印,且为十六进制,因为地址为十六进制表示
    所以最终结果:
    -4的无符号类型值:0xff ff ff fc;-4

也上机测试结果看看:
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第13张图片


第六题

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

分析过程:

  1. 首先得明白 &aa,表示整个数组得大小,对其加减操作,跳过得是整个数组的地址滴。
  2. 其次得明白,数组名aa表示首元素地址,对于二维数组,要用一维数组得眼观去看待,
    所以aa == a[0];那么*(aa + 1) = a[1];
  3. 最后还有要明白指针的类型,是决定了其能访问的字节个数。

简单的画个内存模型图:
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第14张图片


第七题

#include 
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//最终结果:at;

分析过程:
我们要明白字符串是存放在常量区的,而通过char* 类型指针 指向字符串时候,是把字符串的首地址交给了char*指针维护滴 所以,在这道题中,数组 a 的每个元素类型为 char* ,表面看到的是三个
字符串 work ,at,alibaba, 存储在数组中,其实只是存储的是这个三个字串的首地址。
如下图:
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第15张图片
所以,当我们以%s形式打印 *pa时候,打印的就是字符串"at".


第八题

这道题,是检验你能力的时候到了,明不明白指针就看这个复杂的题目了,充分的考察了对指针的理解程度。

int main()
{
char *c[] = {"ENTER","NEW","ROINT","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;
}

让我来好好分析一波:

char *c[] = {"ENTER","NEW","POINT","FIRST"};
首先看这个字符串数组 c ,由于上一题有经验了,所以我们很清楚内存布局是什么:

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第16张图片


char**cp[] = {c+3,c+2,c+1,c};
这句代码又是什么意思呢? 首先我们知道数组 cp 的每个元素的类型为 char**char* 多一个级别,
而 初始化该 cp数组又是 char* 类型的数组名, 数组名是首元素地址,所以这里的 c+3,c+2,c+1,c 都是地址。
我们画出内存布局

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第17张图片


char***cpp = cp;
这句话很明显 把数组名cp赋值给指针 cpp,也就是数组cp的首地址,就是用cpp指向cp数组;

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第18张图片

printf("%s\n", **++cpp);
这个代码最重要的一部分 **++cpp;
这里先++cpp 即 cpp指向了下一位 cp数组的位置,即cp[1]的位置,
然后对其解引用 *++cpp;得到的是cp数组cp[1]的内容:即 c+2;
继续对其解引用**++cpp,得到的就是 c+2地址指向的内容了,即数组 c[2]内容,c[2]的内容还是地址;
由于通过 %s打印,所以找到 c[2]的内容为“ROINT”字符串的首地址,所以打印结果为:“ROINT”

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第19张图片


printf("%s\n", *--*++cpp+3);
这句话核心 *--*++cpp+3; 先算哪个运算符呢?*(--(* (++cpp )))+3
1. 在这里先算 ++cpp ,那么原来cpp指向cp[1]的位置,现在指向 cp[2]的位置了;
2. 在算*++cpp,得到的是cpp指向的内容 即 cp[2]的内容,该内容为 c+1;
3. 继续算 --*++cpp,表示 cp[2]空间的内容 - 1,即c+1 -1得到的是 c,
	那么,也说明了cp[2]的内容不再是 c+1,而是 c, c就是数组c的首地址c[0]的位置;
4. 继续算 *--*++cpp,表示获取 c[0]的内容,该内容为字符串“ENTER”的首地址;
5. 最后算: *--*++cpp+3,就是该字符串“ENTER”从首地址“E”的位置,
	偏移了3个字节位置来到了下一个位置"E",
那么以 %s打印的话:就是从 该位置 “E”,往后打印,最终结果:“ER”;

来看看空间布局:
C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第20张图片


printf("%s\n", *cpp[-2]+3);
核心部分 *cpp[-2] + 3; 等价 **(cpp -2) + 3;
1. 所以 cpp[-2]表示 *(cpp -2),即在内存布局看到的是 cp[0]的内容 c+3;
  注意啊,cpp的指向没有发生变化啊,还是指向 cp[2]的位置;
2. 继续算 *cpp[-2],表示获取 c+3的地址指向的内容,为c[3]的内容为字符串“FIRST”的首地址;
3. 继续算 *cpp[-2]+3,表示从字符串“FIRST”的首地址,“F”的位置,
   偏移 3 个字节,得到的是"S"的首地址,以%s打印,最终结果为:ST	

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第21张图片


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

在这里 ,我们要知道等价关系cpp[-1][-1] +1 == *( *(cpp -1) -1 ) + 1;
接下来就很好分析了:
1. cpp[-1]表示 *(cpp -1),从内存布局可以看到就是 cp[1]的内容,为 c+2;
2. cpp[-1][-1]表示 *( *(cpp -1) -1 ),即为 c+ 2 -1 = c+1的地址内容; 就是 c+1指向的内容
	为:"NEW"字符串的首地址N;
3.  cpp[-1][-1]+1,自然而然就是字符串“NEW”的首地址从“N”的位置,偏移 1 个字节,来到了 E的地址;
4.  所以以 %s,打印最终结果为:“EW”.

C语言指针的那些事:第四篇(最终篇)(笔试面试题,对指针的高度理解)_第22张图片

总结:这道题终于结束了,最终分析过程大家也看到了,其实也不是很难,就是逻辑链长了一点点而已。


总结

其实这 8道题,是真的需要对指针有一定的理解才会完完全全做对,我们对指针,画图是最好理解的方式。我们在这里要了解的主要内容总结一下:

  1. 指针的类型是什么?指针的类型决定了什么?
  2. 对指针解引用需要注意什么?解引用得到的是什么?
  3. 指针加减正数得到的是什么?
  4. 指针减指针得到的是什么?
  5. 指针和地址可以说是一个东西嘛?
  6. 二维数组如何看待?
  7. 字符串数组如何看待?
  8. 多级指针指向如何看待?
  9. 运算符的先后关系,指针的运算如何受运算符的影响?
  10. 数组名是什么?取地址数组名代表什么?数组名加减正数代表什么?
  11. 数组和指针的关系如何理解?
  12. 数据存储模式大小端?如何读取数据,如何存数据?
  13. 内存会不会看?如何优雅的使用指针访问内存的?
  14. 指针的声明如何看待?

看看你们回答不回答得了这些问题,假如可以得话,是真的对指针有了不错的认识哦。
(ps:问题没答案,自由发挥,抛砖引用罢了。)

你可能感兴趣的:(C语言,指针,指针面试笔试题,c语言)