发布时间:2023-08-07 19:00
✨作者:@平凡的人1
✨专栏:《C语言从0到1》
✨一句话:凡是过往,皆为序章
✨说明: 过去无可挽回, 未来可以改变
简单回顾一下,我们上一篇的内容:主要介绍了指针与数组笔试题目。这一篇,我们继续趁热打铁,主要介绍——8道指针笔试题,不说多的,直接开整
#include
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
代码解析:
#include
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
//&a取出的是整个数组,&a+1既跳过了整个数组
printf("%d,%d", *(a + 1), *(ptr - 1));//2,5
//*(a+1)就是第二个元素2,*(ptr-1)就是第5个元素
return 0;
}
#include
//涉及到结构体内存对齐问题,结构体大小为20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
代码解析:
#include
//涉及到结构体内存对齐问题,结构体大小为20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
printf("%p\n", p + 0x1);//0x10014
//p是结构体,结构体大小为20个字节,对于16进制来说,20相当于14
//所以结果为0x100014
printf("%p\n", (unsigned long)p + 0x1);//0x100001
//p被强制转换成为了unsigned long类型,结果为0x100001
printf("%p\n", (unsigned int*)p + 0x1);//0x100004
//无符号整型指针+1跳过一个整型变量
//相当于+4
return 0;
}
我们不妨来试一试运行的结果:深度还原成题目
#include
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;
}
代码解析:
#include
int main()
{
int a[4] = { 1, 2, 3, 4 };
//假设以小端存储方式
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
int* ptr1 = (int*)(&a + 1);
//&a+1相当于跳过了整个数组,到达了4之后的位置,然后强制转化成int*
//ptr1也指向那里
int* ptr2 = (int*)((int)a + 1);
//这里的a被强制转化成int型,整型+1就是+1,差了一个1就1个字节
//然后又被强转为int*,所以ptr2指向第一个元素的第二个字节位置处,与第一个元素差一个字节
//ptr2是一个整型指针
printf("%x,%x", ptr1[-1], *ptr2);//4,2000
//ptar[-1]可以理解为*(ptr1+(-1)),又ptr1是整型指针4个字节,-1跳到0x 00 00 00 04
//对ptr2解引用后向后访问4个字节,又以%x打印,所以是0x 02 00 00 00
return 0;
}
方便理解,画个图:
可能有人不信,看一下运行结果:
#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,3,5
所以实际上二维数组存储的元素是:
#include
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
//a[0]代表首元素地址,就是1的地址
printf("%d", p[0]);//1
//p[0]可以看作*(p+0)就是1
return 0;
}
运行一下结果:
这道题本身并不难,比较坑,我们要知道逗号表达式,知道实际上二维数组存放的元素是什么,这是解题的关键之处
#include
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;
}
代码解析:
为了方便大家的理解,画了个图:
对于a[4][2]:
对于p:
p被赋值为a,a是数组名首元素地址,就是a[0],但是a[0]是5个元素地址,但是p却是4个元素地址。类型存在差异.我们画图来理解p是怎么一回事:
p[4][2]:(就是黄色区域)
回到题目,指针-指针得到的是元素的个数,然后呢?一张图解决这道题:
我们不妨来看看运行结果:
#include
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;
}
通过上面的练习之后,如果你真的彻底理解完之后,这道题就比较容易理解了:
对于二维数组:
1 2 3 4 5
6 7 8 9 10
为了方便理解,还是画图:
对于ptr1来说:
对于ptr2:
#include
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));//10,5
return 0;
}
测试运行结果:
#include
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
这道题画图比较好理解:
pa++就是指向下一位,指向at的位置,所以打印出来的结果为at
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;
}
这道题是真的有趣,真的有趣
代码解析:
*++cpp:第一次,++cpp指向c+2,c+2指向point地址,第二*得到point
*–*++cpp+3:++cpp指向c+1的地址,解引用找到c+1,–就把c+1变成了c,c指向ENTER的地址,在解引用,得到ENTER,+3就是ENTER第三个位置开始,得到ER。
*cpp[-2]+3:相当于* *(cpp-2)+3.首先我们要知道经过前面两个前置++之后,cpp指向了第3个元素的地址,现在-2相当于打回原形,指向cp第一个元素的地址,第一次解引用指向c+3,在一次解引用得到FIRST,+3指向ST,所以结果为ST
cpp[-1][-1]+1:相当于*(*(cpp-1)-1)+1:上面的那次并没有自增自减,所以还是第三个元素的地址,cpp-1指向第二个元素的地址,解引用得到c+2,-1得到c+1,c+1就是NEW的地址,在解引用得到NEW,+1得到EW。
至此,这个代码的解析就到这里结束了
实际上,如果我们对指针的知识有了基础之后,这些就是水到渠成,这上面八道题目的练习,更是让你锦上添花,如虎添翼,训练并巩固了C指针的核心知识点,让我们对指针有了更深层次的认知。
**同时,我们要知道:对于指针的一些题目,我们要善于画图,画图是解决问题关键的一步,这是我们所必须具备的,这是关键步骤,不要忽视了画图!**