二、 指针笔试题
学会画图分析以下各题,更加容易让我们理解其代码的逻辑。所以画图是必不可少的!
1. 第一题
#include <stdio.h> 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拿到整个数组的地址,类型是int * [5],&a + 1就跳过了5个int元素的大小,强制类型转换为int *,ptr - 1就指向了5,解引用操作打印5,*(a + 1),是数组首元素地址 + 1,解引用打印2
2. 第二题
//以下结构体的大小是20个字节 struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }* p; //假设p 的值为0x100000。 如下表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 #include <stdio.h> int main() { p = (struct Test*)0x100000; printf("%p\n", p + 0x1);//1 printf("%p\n", (unsigned long)p + 0x1);//2 printf("%p\n", (unsigned int*)p + 0x1);//3 return 0; }
分析:
0x1 的意思是将十进制的1转换为了十六进制表示法。
//1: p此时是一个结构体,p + 1跳过了一个结构体的大小,跳过20个字节,即0x100020,而地址需要按十六进制打印需要打印八位,高位会补0,所以打印得到00100020
//2: 将p这个指针类型强制转换为 unsigned long 类型,即一个整型(int),那么 + 1,此时加的就是一个数字1,即0x100001
//3: 将p这个指针类型强制转换为 unsigned int*类型,+ 1跳过一个int*类型的指针,即0x100004
3. 第三题
#include<stdio.h> int main() { int a[4] = { 1, 2, 3, 4 }; int *ptr1 = (int *)(&a + 1);//1 int *ptr2 = (int *)((int)a + 1);//2 printf( "%x,%x", ptr1[-1], *ptr2);//3 return 0; }
分析:
//1: 这里的&a拿到的就是整个数组的地址,&a + 1就跳过了一个数组,本来是int * [4]这个类型,强制类型转换成了int * ,ptr1 此时就成了一个int * 类型的指针。
//2: 这里的a是数组名,强制类型转换为整型(int),+ 1,就是加上数值1,也就是一个字节,假设a的地址是0x0012ff40,那么(int)a + 1得到的结果就是0x0012ff41,再次强制类型转换为(int *),放到整型指针ptr2之中,ptr2此时指向了内存中第二个字节的位置。
//3: (1) %x表示是用16进制进行打印输出,ptr1[-1]可以看作为*(ptr1 - 1),即指针ptr1向前跳转一个整型的大小,解引用操作,访问的就是04 00 00 00,打印出来就是4;
(2)ptr2指向了第一个元素的第二个字节处,解引用操作,访问四个字节的大小,且数组在内存中是连续存储的,如图中,灰色底纹部分就是访问的全部空间,即02 00 00 00,将其%x打印就是2000000
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; }
老铁们,乍一看上去是不是觉得就是0了呢?哈哈哈,认为是0的老铁就踩上坑了咯~
为什么呢?下面就对题进行分析吧~
分析:
观察int a[3][2]这个数组在初始化的时候,写成了 { (0, 1), (2, 3), (4, 5) }; ,而不是{ {0,1} ,{2,3}, {4,5} };,注意里面写的是括号表达式,而不是以{ }的形式表示,括号表达式的运算法则是,里面的表达式从左向右计算,最后一个表达式的结果为整个逗号表达式的结果,所以数组初始化元素应该是{1,3,5}; ,其余部分元素均为0
a[0]即第一行的数组名,数组名是数组首元素的地址,即元素1的地址,存放到指针变量p当中去,p[0]就是*(p + 0),即对p进行解引用操作,拿到的就是1,所以结果打印1
5. 第五题
#include<stdio.h> int main() { int a[5][5]; int(*p)[4]; p = a;//1 printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//2 return 0; }
分析:
//1: p = a,这里的a就是数组首元素的地址,即第一行的地址,类型是int (*) [5],但是p的类型是int (*) [4],类型不同会造成什么影响吗?其实不会,就比如 int a = 10,char b =20,a = b,这样最后a的结果虽然是20,但是类型还是int类型。所以我们只是把a的地址值赋值给了b
//2: 观察上方画图,可以清晰地知道p指针在数组中的指向位置,p[4][2]即橙黄色方块的位置,&p[4][2] 指向的就是橙黄色方块,a[4][2]即黑色方块,&a[4][2]指向的就是黑色方块,前者与后者都是指针,前者减去后者得到就是中间元素个数,%d结果为 -4,%p的结果打印的是地址,于是会将内存中的补码转换为十六进制数就是地址了,结果是FF FF FF FC
6. 第六题
#include<stdio.h> int main() { int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int *ptr1 = (int *)(&aa + 1);//1 int *ptr2 = (int *)(*(aa + 1));//2 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1)); return 0; }
分析:
//1:&aa拿到整个二维数组的地址,&aa + 1跳过一个二维数组的大小, 强制类型转换成int *类型,ptr1此时也指向了这个位置。*(ptr1 - 1)打印9
//2:aa是数组名,表示数组首元素的地址,即第一行的地址,类型是int (*) [5] ,+1跳过了一个int [5]的大小,指向了第二行,类型也是int (*) [5],*解引用操作,拿到了第二行,强制类型转换为int *,此时ptr2也指向了6,*(ptr2 - 1)打印5
7. 第七题
#include <stdio.h> int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }
分析:
这里给出了一个名为a的指针数组,每个数组的类型是char*类型,实际存放的并不是"work","at","alibaba",其实是字符串首元素的地址,第一个char*指向了'w'的地址,第二个char*指向了'a'的地址,第三个char*指向了'a'的地址;数组名a是数组首元素的地址,char*的地址为char**类型,存放到pa这个二级指针中,pa++就跳过了一个char*的类型,*pa就拿到了数组第二个元素,即'a'的地址,%s打印出at
8. 第八题
#include <stdio.h> 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; }
分析:
//代码的整体逻辑如上图先分析出来了。
//1: **++cpp,首先程序会执行++cpp操作,cpp会跳过一个char**的类型,此时就指向了c+2,而*一次解引用操作,我们拿到了c+2的这块空间里面的数据,即c+2,而c+2又是c数组下标为2的元素空间的地址,对其进行*解引用操作,就拿到了c数组下标为2的空间,这块空间存放了‘P’的地址,%s就会根据P的地址向后打印字符串,打印的结果就是POINT
//2: *-- * ++cpp + 3,乍一看,有这么多的操作符,其实按优先级来算,还是先从++cpp开始计算,此时的cpp不再指向c+2,而是指向了c+1,然后进行*解引用操作,就拿到了c+1这块空间里面的数据,--操作,相当于c+1减去1,那么这块空间的数据就是c了,此时指向关系就再是原来的指向关系了,而是指向了c数组的第一个元素,解引用操作就拿到了数组c的第一个元素,即'E'的地址,最后+3,此时指针指向第二个'E',%s就会根据此时的地址向后打印字符串,结果打印就是ER
//3: *cpp[-2] + 3 ,cpp[-2]可以看作是*(cpp - 2),即 **(cpp - 2)+ 3 ,cpp - 2指向了cp数组的第一个元素的空间,*解引用操作,拿到了c+3这块空间,这块空间指向了c数组的下标为3的元素空间,解引用操作,拿到了这块空间,即'F'的地址,最后+3,指针指向了'S',%s就会根据此时的地址向后打印字符串,结果打印就是ST
//4: cpp[-1][-1] + 1,cpp[-1][-1] 可以看作是*(*(cpp - 1) - 1) ,即 *(*(cpp - 1) - 1),cpp - 1指向了cp数组的第二个元素的空间,*解引用操作,拿到了c+2这块空间, - 1即c+2减去1,那么这块空间的数据就是c+1了,此时指向关系就再是原来的指向关系了,而是指向了c数组的第二个元素,*解引用操作,拿到了这块空间的元素, 即'N'的地址,最后+1,指针指向了'E',%s就会根据此时的地址向后打印字符串,结果打印就是EW
好了,题目就到这里了,如果能对以上指针题目理解通透,那么C语言指针就没多大问题啦,而以上题目基本上需要我们去画图理解!可以看出理解代码的逻辑时画图有多么的重要!~