🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。 🐌 个人主页:蜗牛牛啊 🔥 系列专栏:初出茅庐C语言 ?? 学习格言:眼泪终究流不成海洋,人总要不断成长! 🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹
在我们学习指针之后,应该在实际应用中去理解和掌握它,毕竟实践才是检验真理的唯一标准,我们以后在找工作的过程中免不了会遇到与指针相关的试题,本篇文章可以帮助我们提前了解一些常见的指针考点。在学习这篇文章之前可以根据需要对
指针进行简要复习。
注:本篇文章所有代码均在X86环境下运行。
笔试题1
#include <stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* pa = (int*)(&a + 1);
printf("%d %d", *(a + 1), *(pa-1));
return 0;
}
解析: int* pa = (int*)(&a+1) 中&a取的是整个数组的地址,(&a+1) 的类型为数组指针类型int(*)[5] ,指向的地址跳过一个a数组的大小,(int*) 是将其强制转换为int* 类型的指针,使其与pa的指针类型相同。 由上图可知*(a + 1) 得到的值为2,*(pa-1) 得到的值为5。
笔试题2
#include <stdio.h>
struct stu {
int num;
char* pcname;
short sdata;
char ch[2];
short sarr[4];
}* ps;
int main()
{
ps = (struct stu*)0x100000;
printf("%p\n", ps + 0x1);
printf("%p\n", (unsigned long)ps + 0x1);
printf("%p\n", (unsigned long*)ps + 0x1);
return 0;
}
解析: 本题考察的是指针±整数指针的变化,ps = (struct stu*)0x100000 中ps是结构体指针变量,凡是放在指针变量中的都被当成地址处理,0x100000被强制类型转换为struct stu* 类型的指针,放在ps中,此时0x100000就被当成地址处理。 ps + 0x1 中,0x1 是十六进制数字1,转换成十进制也是1。所以ps+0x1 就是ps+1,意思是ps向后走struct stu*类型的字节大小,如 char* 类型的指针+1就是向后走1个字节,因为char 占1个字节;int* 类型的指针+1就是向后走4个字节,因为int 类型占4个字节。而题中ps是struct stu* 类型,+1就是向后走20个字节。20转换成十六进制为0x14,0x100000+0x14=0x100014。 (unsigned long)ps + 0x1 是将结构体指针变量ps强制类型转换为unsigned long 类型,ps+1就是向后走1个字节,unsigned long 是无符号长整型,整型+1就是+1,所以为0x100001。 (unsigned long*)ps + 0x1 是将无符号长整形ps强制类型转换为unsigned long* 类型,ps+1就是向后走4个字节,因为unsigned long 类型大小为4个字节,最后为0x100004。 %p–输出的是地址,0x表示数字是十六进制,地址往往以十六进制的形式输出,在X86环境下,地址由4个字节组成,转换为地址后,该代码输出结果为00100014 00100001 00100004。
笔试题3
#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;
}
解析: %x- - -以十六进制输出。 (int*)(&a + 1) 中&a 取得是整个数组的地址,它的类型为int(*)[4],&a+1 跳过一个数组的大小,即跳过16个字节,再强制类型转换为(int*) 。ptr[-1] 即*(ptr-1) ,所以输出为4。 (int*)((int)a + 1) 中((int)a+1) 是将a强制类型转换为int 类型后+1,a是数组名,代表着数组首元素的地址。将数组中的元素转换成十六进制后为a[]={0x00000001,0x00000002,0x00000003,0x00000004} ,VS中采用的是小端存储,在内存中表示如下图。 假设一个数的地址以十六进制表示为0x00000015,强制类型转换成整型之后为21,21+1=22,22转换成十六进制为0x00000016,所以强制类型转换成整型之后再+1相当于地址向后移动了1个字节。因此((int)a + 1) 表示a的地址向后移动了1个字节,所以在*ptr2 中,从a的位置向后读4个字节,即00000002,因为是小端存储,在读取时就以小端的方式读,所以结果为20000000。
笔试题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;
}
解析: int a[3][2] = { (0,1),(2,3),(4,5) } 中含有逗号表达式(逗号表达式:按照从左到右依次计算,整个表达式的结果为最后一个表达式的值)。计算之后得到数组为int a[3][2] = { 1,3,5 } ,p = a[0] 将a[0]赋值给整型指针变量p,在二维数组中,把每一行都看成一维数组的时候,a[0]表示第一行的数组名,数组名表示首元素的地址,即a[0]=&a[0][0]。p[0] 中p等于a[0],p[0]等于*(p+0) 等于*(&a[0][0]+0) ,即数组第一个元素的值1。
笔试题5
#include <stdio.h>
int main()
{
int arr[5][5];
int(*p)[4];
p = arr;
printf("%p %d\n", &p[4][2] - &arr[4][2], &p[4][2] - &arr[4][2]);
return 0;
}
解析: 本题考察的是指针-指针。int(*p)[4] 是数组指针类型,p = arr 中arr 是二维数组数组名,数组名就是首元素的地址,在二维数组中首元素地址就是第一行的地址即&arr[0] ,&arr[0] 用int(*)[5] 类型数组指针接收,将int(*)[5] 类型赋给int(*)[4] 类型,可以编译,虽然不会报错但是有警告,因为p是int(*)[4] 类型,p+1 的时候向后走4个整型,具体可见下图: p[4][2] 可以写成*(*(p+4)+2) ,位置如上图所示,指针和指针相减的绝对值是元素之间的个数,&p[4][2] - &arr[4][2] 是低地址-高地址,得到的是-4。 -4的原码、反码和补码如下表所示:
原码、反码、补码 | 值 |
---|
原码 | 10000000000000000000000000000100 | 反码 | 11111111111111111111111111111011 | 补码 | 11111111111111111111111111111100 |
用%d打印的时候打印的是原码。 %p–输出地址,地址没有原码、反码和补码,在内存中存的是补码,因为打印的地址是一个明确的数-4,打印的时候就把补码当成地址打印,补码转换成十六进制就是FFFFFFFC。
笔试题6
#include <stdio.h>
int main()
{
int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)(*(a + 1));
printf("%d %d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
解析: &a+1 跳过整个二维数组,然后强制类型转换为(int*)类型,*((a+1)) 中,a 是二维数组数组名,表示数组首元素的地址,在二维数组中,数组首元素的地址即第一行的地址,第一行的数组名可以用a[0]表示,*(a+1) 可以表示成*(&a[0]+1) ,&a[0]是int(*)[5]类型,+1之后到二维数组的第二行,所以(*(a+1)) 就是a[1] ,a[1]就是第二行数组名,也表示第二行首元素的地址&a[1][0] 。
笔试题7
#include <stdio.h>
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
解析: char* a[] = { "work","at","alibaba" } 中a[] 是字符指针数组,存的是字符串首字符的地址,char** pa 表示pa指向char*类型,char** pa = a 表示pa指向a,a是数组名,表示数组首元素的地址,此时,pa指向数组中的第一个元素,pa++ 表示pa跳过一个char* 类型,则pa此时指向数组中的第二个元素,*pa 取出数组中第二个元素的首地址,打印字符串。
笔试题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);
printf("%s\n", *-- *++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}
解析: c 数组中存的是字符串首字符的地址,c+1 中c 是数组名,数组名表示数组首元素的地址,因为数组c 里面的数据类型是char* 类型的,存放的是字符串首字符的地址,数组首元素就是字符的地址,地址的地址要用二级指针来接收,所以用char** ,c+1 之后指向数组c 中第二个元素的地址,c+2 指向第三个元素的地址,c+3 指向第四个元素的地址,而c 数组中的元素都是字符串首字符的地址。数组cp 中的元素类型都是char** 类型,要用char*** 类型接收。 cpp 指向的类型是 char** ,char*** cpp = cp ,cp 是数组名,表示数组首元素的地址,刚开始三级指针 cpp 指向 cp 数组中的 c+3 的地址,**++cpp 中 ++cpp 之后指向c+2 的地址,第一次解引用 *++cpp (解引用操作得到的是指针指向地址中所存放的内容),得到的是c+2 ,c+2 指向的是数组c 中第三个元素的地址,再次解引用之后得到的是c 数组中存放字符串POINT 首字符的地址,打印字符串得到POINT。
*-- *++cpp+3 中 + 的优先级比++ 、-- 、* 的优先级低,所以+3 放在最后计算, ++cpp 执行后cpp 指向cp 数组c+1 的地址,解引用得到c+1 ,--(c+1) 执行后cp 数组中元素c+1 变为c ,c 指向数组c 中第一个元素的地址,解引用之后得到字符串ENTER 首字符的地址,+3向后移动3个字节,得到ER。
*cpp[-2]+3 等于*(*(cpp-2))+3 ,(cpp-2) 表示指向数组cp 中c+3 的地址,但是cpp 指向的位置没有发生改变,*(cpp-2) 表示解引用之后得到c+3 ,c+3 指向数组c 中第四个元素的地址,再次解引用得到的是字符串FIRST 首字符的地址,+3向后移动三个字节,打印ST。 cpp[-1][-1]+1 等于*(*(cpp-1)-1)+1 ,因为cpp 指向的位置没有发生改变,所以cpp-1 指向数组cp 中c+2 的地址,*(cpp-1) 后得到c+2 ,*(cpp-1)-1 等于(c+2)-1 为c+1 ,c+1 指向数组c 中第二个元素的地址,*(*(cpp-1)-1) 执行后得到字符串NEW 首字符的地址,+1向后移动1个字节指向E,打印字符串为EW。
|