C语言八道笔试题精讲带你掌握指针
作者:龙兆万
为了题目的准确性和我们一般学习过程中的习惯,这里所有的题目代码都是在 X86 环境(32 位平台)下运行的。
题目一
#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; } //程序的结果输出什么?
我们先研究 ptr 指针变量里面存储的是什么。&a+1 表示取出整个地址并向后跳跃一个数组类型的大小,也就是指向了数组最后一个元素 5 的后面一块数组类型大小的空间。
但是我们想要把这个地址交给指针变量 ptr 是不行的,因为类型不一样(&a+1 是一个数组指针类型,prt 是一个整形指针类型),所以我们在 (&a+1) 之前 (int*) 来强制类型转换。这样我们搞清楚了 ptr 指针变量里存的是什么。
那么现在来看输出部分。*(a+1) 是比较简单的,a 是首元素地址,a+1 是第二个元素地址,对其解引用得到第二个元素 2 ,这是毋庸置疑的答案。对于 *(ptr-1) ,我们知道 ptr 里面存放的地址位置,并且知道 ptr 是一个整形指针,ptr-1 相当于向前跳跃一个整形类型的大小,即指向数组最后一个元素 5 的位置。对其解引用就能得到元素 5 。
故最后的输出结果为: 2,5 。
题目二
#include <stdio.h> struct Test { int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p; //如下表表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 int main() { printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; } //程序最后输出什么?
我们注意观察结构体的定义,定义了一个结构体指针变量 p 。
p+0x1 非常简单,0x1 就是 1,只不过是以十六进制的格式书写的。这与我们经常习惯使用的指针计算一样,p 是一个结构体指针,那么 p+1 理当向后跳跃一个结构体类型的大小,即指向了结构体空间的后一个结构体空间的位置。并且我们知道 p 是一个全局变量,全局变量默认初始化为 0 ,那我们便可以知道,p=0 ,p+1=20 。而 %p 是一个十六进制的打印格式,所以结果输出:00000014 。
(unsigned long)p + 0x1 似乎更简单一些。p 类型转换成了一个长整形,那么就代表 p 不再是一个指针,它存放的 0 就是单纯意义上的数字。所以 0 + 0x1 就等于 00000001 。
(unsigned int*)p + 0x1 也非常简单。p 本是一个结构体指针,现在强转为整形指针,这就代表着 p 进行加减整数的时候步幅由 20 个字节 变成了 4 个字节。那么 p=0 ,p+1=4 是毋庸置疑的。输出的结果为:00000004 。
题目三
#include <stdio.h> 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 的分析与题目一没有区别,ptr1 存放的是数组最后一个元素的后面一块数组类型大小的地址。并且 ptr1 是一个整形指针。
ptr1[-1] 可改写成 *(ptr-1) ,因为我们曾经说过,只要是数组下标的操作都是指针操作,数组下标的操作只是指针操作的简写。那么此时 ptr1[-1] 就应该指向数组最后一个元素 4 并对其解引用得到输出结果 00000004 。
(int*)((int)a+1) 如何理解呢?a 是数组名,那么它就指向了数组首元素的地址,再将其强制类型转换,那么 a 里面存放的地址就是单纯意义上的数字,我们在这个数字的基础上 +1 ,然后再强制类型转换成整形指针并存放在 ptr2 中。也就是说,a 本是指向数组首元素的地址,(int)a+1 就代表地址数 +1 ,地址数 +1 就说明如果是指针的话,它就会指向后一个字节。再强转为整形指针,那么此时 ptr2 指向的地址为:
再对其解引用就能找到红色方块那块空间的内容,因为是小端存储,那么输出的结果是:02000000 。
需要注意的是,本题使用的打印格式为 %x ,正儿八经的十六进制,会省略有效数字前的 0 。
题目四
#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; } //程序的结果输出什么?
首先我们注意观察,二维数组 a 里面存放的是三个逗号表达式,我相信有人会在这里踩坑。即三个逗号表达式,那么二维数组 a 里面的内容应该是 1,3,5 ,0,0,0 (数组 a 有 6 个元素,内容不够后面自动补 0 )。
搞清了二维数组存放的是什么之后,我们来研究 a[0] 是什么。a[0] 是二维数组的第一个元素,这个元素是一个数组,也就是说,指针变量 p 存放的是二维数组中的第一个元素,这个元素是一个一维数组,即拿到了一个数组名。那么 p[0] 就很好理解了,p 是一个数组名,p[0] 就是数组的首元素,即 1 。故输出 1 。
题目五
#include <stdio.h> 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 存放了二维数组 a 的首元素地址,这就不难分析了,p 指向的数组与 a 数组的存储内容一样,但是内存分配不一样。
我们把这两个空间重叠在一起:
可以看到,&p[4][2] 与 &a[4][2] 相差了 4 个元素,但因为 &p[4][2] 的地址位置比较低,所以两个相减是一个负数,即 -4 。但我们要以 %p 的形式打印,%p 是一个无符号的数,所以我们便得知结果应该是一个非常大的十六进制数。那么 -4 的原反补为:
所以前者的输出结果为:fffffffc 。
我们知道,指针(地址)相减,得到的是相差的元素个数,上面我们也讲过。那么 %d 是一个有符号的打印格式,所以输出的结果就是 -4 。
题目六
#include <stdio.h> 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; } //程序的结果输出什么?
*(ptr1-1) 的结果为 10 应该没什么意见吧?
*(aa+1) 可改写成 aa[1] ,也就是二维数组的第二个元素,这个元素是一个一维数组,也就是说得到了一个数组名。将这个数组名强转为整形指针存放在指针变量 ptr2 中,这时候就知道了 ptr2 的具体指向位置。(二维数组在内存空间中是连续存放的!)
*(ptr2-1) 就是将 ptr2 向前跳跃一个整形类型的大小,并解引用,即找到了元素 5 ,所以输出结果为 5 。
题目七
#include <stdio.h> int main() { char* a[] = { "work","at","alibaba" }; char** pa = a; pa++; printf("%s\n", *pa); return 0; } //程序的结果输出什么?
指针数组 a 存放了三个字符串常量(关于字符串的常量在往期的博客中已经介绍过),使用二级指针 pa 来指向这个数组(char** pa=a;),这就表明 pa 变量存放的是 a 数组的首元素地址。pa++ 指向了数组的第二个元素,*pa 毋庸置疑得到了字符串常量 "at" 的首元素地址,再通过 %s 打印,就能的得到 at 。
题目八
#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); printf("%s\n", *-- * ++cpp + 3); printf("%s\n", *cpp[-2] + 3); printf("%s\n", cpp[-1][-1] + 1); return 0; } //程序的结果输出什么?
我们先分析一下 c、cp、cpp 里面存放的是什么东西。指针数组 c 里面存放有四个字符串常量。指针数组 cp 里面存放的是四个字符串常量的地址。三级指针 cpp 存放的是 指针数组 cp 的首元素地址。那么草图可以画成:
**++cpp ,因为结合性的原因,我们要先计算 ++cpp,那么此时 cpp 指向 cp 的第二个元素的地址,然后解引用得到了 cp 中第二个元素,这个元素又是指向 c 的第三个元素,然后再解引用得到 c 的第三个元素,这个元素是字符串常量 "POINT" 的首字符地址,通过 %s 的形式打印得到 POINT 。一定要注意,++ 这个操作是 cpp 参与运算了,所以下次运算时会从计算后的位置开始。
*--*++cpp+3 ,还是先计算 ++cpp ,此时 cpp 指向 cp 的第三个元素的地址,然后解引用得到 c+1 ,然后 -- ,c+1 就变成了 c ,然后解引用得到 c 的首元素地址,在此地址的基础上 +3 ,就拿到了字符串常量 "ENTER" 的第四个字符的地址,然后通过 %s 的形式打印得到 ER 。
*cpp[-2]+3 可改写为 *(*(cpp-2))+3 ,也就是说,此时 cpp 指向了 cp 的首元素地址,解引用得到了 c+3 ,c+3 是指向 c 的第四个元素的地址,解引用便得到了 c 的第四个元素,这个元素是字符串常量 "FIRST" 的首元素地址,在此地址的基础上 +3 便得到字符串常量第四个字符的地址,%s 通过这个地址打印便得到 ST 。(此时 cpp 是没有实质运算的)
cpp[-1][-1]+1 可改写为 *(*(cpp-1)-1)+1 ,此时 cpp 指向 cp 的第二个元素的地址,解引用得到 c+2 ,然后 -1 得到 c+1 ,即指向了 c 的第二个元素的地址,解引用便得到了 c 的第二个元素,这个元素是字符串常量 "NEW" 的首元素地址,然后 +1 便指向了字符串常量的第二个字符的地址,通过 %s 形式打印便得到了 EW 。
到此这篇关于C语言八道笔试题精讲带你掌握指针的文章就介绍到这了,更多相关C语言指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!