前言:
小编感觉最近有点太堕落,于是我开始从事这篇文章的撰写,现在也是进入七月份了,我现在文章开头定一个小目标,我决定在七月份发布至少十篇文章,希望我可以说到做到(我前面就口头欠了不少文章),好了,不多废话了,下面进入今天的主题,对于指针习题的演练,这些题都是小编在上课时听到的题,为了加强自己的记忆,于是我将此作为文章的主题,下面进入正文喽!
目录:
1.题目一
2.题目二
3.题目三
4.题目四
5.题目五
6.题目六
7.题目七
正文:
1.题目一
int main()
{int a[5] = { 1, 2, 3, 4, 5 };int* ptr = (int*)(&a + 1);printf("%d,%d", *(a + 1), *(ptr - 1));return 0;
}
//程序的结果是什么?
各位先来思考一下这个题怎么做,老规矩,小编依旧是先解释,后做题,下面进入解释环节:首先我们先看printf函数,里面第一个元素是*(a + 1),此时a是数组名,它不是那两种特殊情况,所以此时数组名代表的就是数组第一个元素,加1后对应着数组第二个元素的地址,在进行解引用操作后,会直接呈现第二个元素,那么就是2了;之后是第二个元素,此时涉及到了ptr,所以我们先看ptr这个指针,它代表着进行整型强制类型转换以后的(&a + 1),不难发现,此时数组名符合了第一种特殊情况,所以代表着整个数组的地址,加1后是数组最后一个元素下行的地址,此时指针指向的应该是5后面元素的地址,此时减去1后,又回到了数组第五个元素代表的地址,所以进行解引用操作后,变成了5,为了让读者更好的理解这个题目,小编特意的给了图例进行解释:
通过上图可以清晰的看出小编在文字解释中所解释的内容,下面小编展示这个代码的运行图:
可以看出小编解释的是正确定,下面废话不多说,进入下一个题的训练:
2.题目二
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{int Num;char* pcName;short sDate;char cha[2];short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{printf("%p\n", p + 0x1);printf("%p\n", (unsigned long)p + 0x1);printf("%p\n", (unsigned int*)p + 0x1);return 0;
}
老规矩,大家先思考几秒,然后给出解释,下面开始解释环节:首先映入我们眼帘的是一个结构体,由于结构体的大小如何计算小编还没有设计到,所以这里直接给大小了(后面小编会着重强调它的对齐方式的在结构体的文章中),这里我们已经给出了结构体的地址,不过这个地址得强制类型转换,不然就不是结构体的地址了,下面我们正是看看函数部分,首先是第一个printf函数,这个函数内部是结构体指针加1,记住这个加的1是0x开头的,所以应该是16进制的方式进行操作的,此时p是指针,指针加数的话代表着它的地址是要加它本身大小倍数的数,所以此时应该加20个字节,并且由于这是16进制储存的,所最后的结果应该是0x100014;对于第二个printf函数,首先我们先看括号内容,此时括号内容是unsigned long,所以此时的p已经强制类型转换成无符号的长整型了,此时p可以看作一个整数,对于整数加1的话,那就是真的加1,所以结果应该是0x100001;对于第三个printf语句,此时括号里面的内容是unsigned int*,代表着强制类型转换成无符号整型的指针,所以此时指针每次加的大小已经改变了,此时加1代表着加4个字节的空间,所以结果应该是0x100004,这个问题小编认为不需要图来了解,因为这没有牵扯到指针的迁移,仅仅就牵扯到了指针运算,下面来看看此代码的运行图:
读者朋友无需关注前面是什么,光看末尾数字就好了,此时说明小编解释没有错误,那么我们加快步伐,直接开始看下一个题的讲解!
3.题目三
int main()
{int a[3][2] = { (0, 1), (2, 3), (4, 5) };int* p;p = a[0];printf("%d", p[0]);return 0;
}
老规矩,各位先看会题目,我等会做出解释,下面开始解释:首先这个题定义的二维数组,然后设置了一个整型指针,此时指针指向二维数组第一行的地址,然后printf函数里面的是p[0],此时其实有第二种写法,就是*(*p + 0),此时不难看出,这个是指向数组第一个元素的地址,那么我们回头看看这个二维数组,可能有些读者会脱口而出,答案是0,但是其实这个说法是错误的,仔细看,数组里面每两个元素用什么围起来的,是括号,而不是大括号!所以此时我们对于括号里面的内容,其实是逗号表达式,逗号表达式的逻辑是,从左往右依次计算,不过最后的结果是右边的数,所以其实这个数组在内存中的存放是如下图所示的:
所以可以知道打印出来的结果应该是1,那么很多读者可能忘记了我们想要规定二维数组行和列具体数应该怎么做,其实这里是要用大括号的,下面是代码展示:
int a[3][2] = { {0,1 }, {2,3} , {4,5} };
下面是在调试界面此数组的存放:
所以各位在做相关习题的时候一定要瞪大双眼,防止出题人给你设置成一个大坑让你跳进去,我们要做到看到坑直接绕过去,下面不多废话直接上运行页:
可以看出小编并没有说错,大家一定要在做题不要看题目过于简单而痛失分数。下面不多废话,调整心态,下个题来喽:
4.题目四
//假设环境是x86环境,程序输出的结果是啥?
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数组的存放图文来帮助大家去感受a数组内容:
下面我们来正式开始观看这个代码,首先我们看到一个老朋友:int (*p)[4],读者朋友如果已经学完了指针部分,那么很容易看得出这个是数组指针,p代表的是数组,而[4]则代表着里面存放着四个元素,下面我们可以接着看,此时把a首行的地址给了p,此时读者朋友可能会想,a每个里面存放着5个元素,而p里面却存放着4个元素,它们如何建立起联系呢?其实这里不用多考虑,首先,p其实在这里我们可以看做成地头蛇,有句老话这么说,在我的地盘,是龙只能盘着,是虎也得趴着,所以此时每个p里面都存放每个a所对应的四个元素,此时小编也将p在内存中的存放也放到下面了:
下面我们已经画好了a和p的图,此题其实已经完成了大半部分了,下面我们来看看printf函数里面的内容,这两个元素是一样的,只不过是两种形式表示着,一个是地址,一个是整型,下面我们先来看看元素是什么,分别是p[4][2]和a[4][2],如果没有画图,可能很难看出来这个具体表示着什么,但是现在我们有了图,这就好办了,下面是小编利用图来表示这两个元素
此时printf里面存放着是这两个数的地址,小编之前说过,指针和指针相减,得出来的结果是两个指针之间相差的元素的个数,这个小编之前写过的文章说过,下面放上链接,感到兴趣的读者朋友可以看一下:深入理解并打败C语言难关之一————指针(1)-CSDN博客,回归正题,这两个数之间相差的个数是4个,但这个是前者减去后者,所以得出来的结果应该是-4,不过现在的重点是第一个元素是要取到它的地址,所以这里牵扯到了原码反码补码的知识,这些知识小编也会在以后的文章说的(欠的文章+1),所以下面通过代码页来展示-4的反码:
//10000000000000000000000000000100 //原码
//11111111111111111111111111111011 //反码 (原码取反)
//11111111111111111111111111111100 //补码(反码 + 1)
所以的出来结果应该是下面源码4个为一起通过16进制位数表示出来,应该是0xFFFFFFFC,下面这个题已经讲解完毕,下面进入此题目的运行环节:
可以看出小编这里并没有说错,这个题有点复杂,读者朋友们如果理解不了的话一定要多看一遍,下面趁热打铁,进入下个题目喽!
5.题目五
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;
}
如果会了上一个题的话,这个题也是会迎刃而解的,老规矩,读者朋友先思考下这个题,等会小编做出解释,三,二,一,下面给出解释:
首先,印入我们眼帘的还是一个二维数组,下面小编先来画一下二维数组的储存图来帮助各位取理解:
这个二维数组的存放就是如图所示的(为了直观小编直接展开成一行了),我们开始看代码页,首先我们先看到的是两个指针,ptr1和ptr2,首先第一个是先对数组名进行取地址操作,此时涉及到了小编之前提到过的特殊情况,所以此时是取到了整个数组的地址,在进行加1就是去到了10以后元素的地址,此时还对他强制类型转化成了整型指针(可能很多读者朋友会很奇怪,认为二维数组不也是整型指针吗,其实二维数组实质是一个数组指针,小编以前可能提到过,如果没提到过大家记住就好),在看ptr2,此时这个指针同样也是对括号里面的内容强制类型转化成整形指针了,下面我们再来看看括号里面的内容,此时是先对a进行加1操作,我们知道,二维数组数组名代表着第一行的地址,所以这个进行加1操作以后直接变成了a[1],在进行解引用操作以后直接变成了第一个元素所代表的地址了,下面为了让读者朋友更好的理解这个题目,直接上图:
之后我们继续往下看,我们来看看printf函数,此时里面同样也是有两个元素,第一个是对ptr1 - 1,此时ptr已经是整形指针了,所以减1后对应着10这个元素的地址,然后解引用后会变成10;第二个是ptr2 - 1 ,此时ptr2也是整形指针,减去1后代表着5的地址,解引用后是5,下面我们先用图文解释,在来放运行图:
可以看出小编并没有说错,大家在写题目的时候一定要画图,画图可以保证大家做题目时更有思路,提高正确率,下面我们进入下一道题:
6.题目六
int main()
{char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;}
这个题可能会有一点难度,读者朋友一定要先仔细看看这个题,画出相应的图这个题就好;理解多了,下面小编放上这个题的解析:
首先,我们看到了一个数组我,这个数组的类型是char *,所以很显然这个数组是个指针数组,下面我们图解这个指针数组:
通过上图可以清晰的看出这个指针数组的具体内容,之后我们接着看代码,后面的代码是又设置了一个二级指针pa,这个二级指针储存着此指针数组首元素地址的地址,接着看下面的代码,此时是pa++,代表着pa + 1,所以此二级指针变成了储存第二个元素的地址的地址,然后我们继续看下面代码,看出其元素是对二级指针进行解引用,我们知道,二级指针解引用后就变成了其储存元素的地址,此时由于储存着字符串,对字符串的打印我们需要获取字符串的地址,此时正好是第二个元素的地址,所以应该打印出的是"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;
}
这个题的难度比上面的难度还要大,大家先看看这个代码,自己画图思考一下,下面小编着重进行解释了:
首先,我们不难发现了这里牵扯到了三个指针,分别是指针数组,二级指针,三级指针,各位先看好这几个指针,下面小编直接上这三个指针的图解了,毕竟一个一个写显得我有点水字数了:
下面我们来看看printf语句里都有什么元素,首先看第一个printf里面的元素,里面是 ** ++ cpp,首先我们知道++操作符运算级是很高的,所以此时是cpp先 加 1,然后此时指向了cp[1]的地址,之后二次解引用操作后,变成指向了c[2]的地址,所以是打印了c[2]所代表的字符串,也就是"POINT",下面小编给上图解:
下面我们再来看看第二个printf函数里面的元素,这个更是复杂:*-- * ++cpp + 3,此时我们一个一个的看,此时是先进行 ++ cpp的操作,经过上面的printf里面cpp进行加1后,此时在进行加1,cpp指向了cp[2]的地址,此时进行解引用操作后就会变成cp[2],之后我们在进行--操作,此时是代表着cp[2]所指向的内容减1,所以此时cp[2]变成了指向c[0]的地址,再次进行解引用操作以后,此时就是变成了c[0],此时再次进行 + 3操作,代表着数组里面的元素加3,所以我们打印数据就开始从E进行打印,所以最后打印的结果应该是“ER”,下面给上图解: 下面我们继续来看看下一行的代码,此时里面的元素是 : *cpp[-2] + 3 ,我们一九从左往右看,此时是先对于cpp进行解引用操作,由于上面进行了两次 ++ 操作,所以解引用cpp以后里面的元素变成了cp[2],此时有一个[]操作符,其实这个就是再次对cp进行解引用,不过是减2后再解引用,所以此时变成了cp[0],进行解引用后就指向了c[3],此时再次加3代表着里面元素地址加3,所以是从“S”开始打印,最后打印出来的应该是:"ST",下面依旧给上图解来帮助大家理解:
下面我们再来看最后一个printf函数里面的元素,里面是:cpp[-1][-1] + 1 ,我们依旧从左往右看,此时cpp[-1]其实实际代表着* cpp - 1,所以此时是代表着cp[1],之后的[- 1],代表着 * cp[1] - 1,也就是说最后指向的是c[1],此时在进行 + 1操作后,就代表着从“E”开始进行打印,最后呈现的结果是 “EW”,下面我们依旧用图文进行解释:
下面我们来放运行结果:
可以看出雄小编这个题并没有说错,这个题大家一定要好好的去了解,难度系数还是比较大的,这个题也是同样彰显了画图的重要性,大家在做题时一定要好图,这样才不容易出错,也好找到自己的错误。
总结:
小编也是干完了这篇文章,这也是指针部分的强化篇,指针讲解部分到这里就彻底的结束了,相信各位读者朋友看到小编写过的指针篇幅,大概也知道了指针的重要性,大家一定要好好掌握指针,到现在开始,我们已经返过了C语言的一座大山,希望各位已经明白了指针。最后,如果文章有什么错误,请大家一定在评论区指出,小编会改正自己错误的,那么,我们下篇博客见啦!