哈喽,大家好啊,我是情谊,今天我们来讨论一下几道关于指针的经典例题,我相信,掌握了这几道题后,大家的指针已经基本掌握了,那么,话不多说,我们直接来看看吧!
首先,我需要向大家解释一下,这几道题非常的经典,我希望大家还是能够尽力掌握这些题型,然后我希望大家都能够知道:数组名只在两种情况下表示整个数组,其他所有情况下都表示首元素的地址,这两种表示整个数组的情况:1直接用sizeof求取数组的大小 2使用&符号时,如何解释这两种情况,我们在下面的题型中在进行说明。
一,字符数组
1strelen求取数组长度
大家先看看上面这个程序,我们创建了一个字符数组,然后求取不同情况下的字符长度。
先看第一个,printf("%d\n",strlen(arr)),在这个代码中,arr表示首元素的地址,数组中没有\0,那么就会导致数组越界访问,结果就会是随机值。
第二句,一样的方法,也是表示数组首元素的地址上加0,还是没有\0,越界访问。
第三句,我们要知道,size_t strlen(const char * s),arr是首元素的地址,*arr则是对数组进行解引用,即*arr = 'a',但是这里我们使用的是a的ASCLL码值,是97,所以这句话的意思是求取指向97这个地址的空间的长度,但是97这个地址是不能够被访问的,所以这一步就会直接程序崩溃。
第四句,arr[1]表示的是arr的第二个元素,即是’b',ASCLL码值是98,也是不能够访问的,程序就会崩溃
第五句,&arr中&取出的就是arr这个数组中的所有的元素,但是也依然用首元素的第一个地址来表示,但情况不同的是,这个代表的是整个数组的情况,操作时,也会进行整个数组的操作,所以在这个代码中,&arr传入的还是首元素的地址,所以求取的还是从第一个元素向后\0个元素,故值是随机值。
第六句,&arr表示的是整个数组,在整个数组的情况下加上一个一,就直接跳到数组最后一个元素的下一个空间的地址去了,即还是随机值,不同点是这个随机值是从数组的下一个空间算起的
第七句,&arr[0]+1,arr[0]表示的是字符‘a',&arr[0]则表示将a的地址取出来,然后加上一,也是随机值,不过是从数组的第二个元素向下数的。
这道题,我们从不同的方向介绍了什么情况下数组表示整个数组,什么情况下数组表示首元素地址,在数组中,上面的规则统一适用!
2sizeof求取数组大小
为了巩固数组关于指针的知识,我们接下来看看上面这个代码。
第一行,arr单独遇到sizeof表示的是整个数组,即求取的是整个数组的大小,包括\0,即结果为7
第二行,arr+0不再是单独的数组名遇见sizeof,所以在这里表示的是数组首元素,arr+0表示的还是数组首元素地址,所以求取的大小为指针的大小,结果是4/8
第三行,arr表示的是首元素的地址,*arr对首元素的地址解引用得到的就是‘a',结果是1
第四行,arr[1]表示的是’b‘,所以结果是1
第五行,&arr表示的是整个数组的地址,所以还是求取的指针的大小,故是4/8
第六行,&arr代表的是整个数组,所以&arr+1就会跳过整个数组,指向数组最后一个元素的下一个地址,地址就是指针的大小,所以值也是4/8
第七行,&arr[0]+1代表的是取出arr[0]的地址进行加一,故求取的是第二个元素的指针大小,结果还是4/8
综上上面这两道题,我们可以看出指针在数组上的特点,学习指针我们一定要区分清楚这些易错的问题!
二,二维数组
1sizeof求取二维数组的大小
再看上面这个代码之前,我们需要知道,二维数组本质上是由一维数组组合成的集合,其具体表现方式如下图,需要注意的是,我们可以将二维数组想象成为一个洋葱,剥开他的一层就是一维数组的集合,再剥开一层就是每一个元素的集合,也可以理解为,二维数组的元素就是一维数组。
那么我们接下来看代码
第一行,a直接遇见sizeof表示的是整个数组,故求取的是整个数组的大小
第二行,a[0][0],表示的是二维数组的第一个元素,求取的是该值的大小
第三行,a[0]表示的是二维数组的第一个元素的地址,上图的a[0]这一个一维数组的地址,但是地址的大小是固定的,为4/8
第四行,因为二维数组的元素是一维数组,所以a[0]+1跳过的是一个一维数组的长度,但是,依然是地址,所以大小依然是4/8
第五行,*(a[0]+1),再第四行的基础上解引用, 求取的就是a[1]这个一维数组的大小了
第六行,(a+1)中的a表示首元素的地址,+1就是a[1]的地址了,但是此处依然是地址,大小是4/8
第七行,*(a+1)是在第六行的基础上解引用,求取的就是a[1]整个一维数组的大小了
第八行,&a[0]+1,将a[0]的地址取出加上一,表示的是a[1]的地址,但是地址依然是4/8
第九行,*(&a[0]+1)是将a[1]解引用,求取a[1]的大小
第十行,*a,a表示首元素的地址,那么*a就表示首元素,即是a[0],所以求取的就是a[0]的大小
第十一行,a[3],上面代码中没有a[3]的数组,只有a[0],a[1],a[2],所以这里的地址相当于野指针,但是依然是地址,所以不管怎么样,地址的大小都是4/8
2二维数组之间的减法
在上面的代码中,a、是一个二维数组,p是一个数组指针,p=a表示的是将a中的首元素的地址给p,所以p存储的就是a[0]的地址,p[4][2]也可以写成*(*(p+4)+2), p是a[0]的地址,但是注意!!p+1加的是p类型的长度,因为p的类型是数组指针类型,结合该题所以加一就是加上一个四,如下图,所以*(*(p+4)+2)的值就是图中紫色的图形,同理,a[4][2]的地址就是绿色的图形,所以两者相减,计算的就是两者地址之间的元素个数
上面这一道二维数组的指针题目希望大小一定好好理解一下,一定要知道这些不同求取,让你求的是什么!
三,数组指针
上面这个是一个简单的数组指针类型的题,我们来看一看吧
a是一个数组,&a则表示的是取出的整个数组的地址,&a+1则就是指向a最后一个元素的下一个空间的地址,但是我们注意到,&a+1的类型是int*[ ] ,表示的是数组指针,而ptr的类型则是int*,所以直接将&a+1的结果传给ptr就会报错,所以我们要将&a+1强转为int*才能赋值,接着,我们计算 *(a+1), a表示首元素的地址,+1则表示第二个元素的地址,再进行解引用就是a[1],结果是2。
*(ptr-1)中ptr是a[5]的地址,-1之后就是a[4]的地址,解引用后结果就是5
在这个代码中,我们主要区分ptr1和ptr2的区别,我们先来看ptr1
ptr1如同上一个代码一样,&取出的是整个数组,&aa+1就是数组最后一个元素的下一个地址
ptr2也是一样的分析方法,aa表示首元素的地址,即是a[0]的地址,a[0]+1就是a[1]的地址
所以接下来打印*(ptr1 - 1)的答案就是10,*(ptr2 - 1)就是5
还是需要注意,在ptr1和ptr2赋值的时候,将&aa+1和*(aa+1)的类型已经改变,所以执行加一和减一的操作时加减的字节数需要改变,也就是说,每次执行的都是该数据类型的长度!!
四,多维指针
上面的代码中a是一个指针数组,存放的是三个字符串的首元素地址,如图
pa是一个二级指针,存储的是a的首元素的地址,pa++跳过一个元素,指向a[1]的地址,再进行解引用就打印出at的地址了
最后,我们再来看一个非常难的题
这道题,也算是一道非常难的题,所以希望大家能够严正以待,其实对待这些题,我们最好的处理方式就是画出程序运行图。
结合上面我们一直强调的知识,我们画出这个代码的运行图,接下来,我们看操作
第一个操作,**++cpp,表示先对cpp进行加一操作,再解引用,cpp表示cp的首元素地址,+1就指向了c+2的地址,解引用求出的是POINT.
第二个操作,*-- * ++cpp + 3,在这个操作中,我们要知道,加法是最s算的,所以先算++cpp
但是还有一个特别注意!!!前置加加会改变数据,即++cpp运算后的值是会重新赋值给cpp的,所以在第一次操作运算完后,cpp指向的应该是c+2的地址!!
++cpp让cpp又指向c+1的地址,进行解引用,就是拿到c+1这个数,再进行--(前置减减)操作,算出就是c,接下来继续解引用,就是NETER的首元素地址,最后进行加三的操作,即N的地址加三,打印出来的就是ER.
第三个操作,*cpp[-2]+3,可以换算成* *(cpp-2) +3,那么像上面的运算方式一样,在上面的代码运算中,cpp指向的是c+1的地址, 所以cpp-2就重新指向c+3,再进行解引用,找到了c+3,再对c+3进行解引用,找到的是FIRST中的F的地址,最后进行+3,打印的就是ST
第四个操作,cpp[-1][-1]+1,可以转换为*(*(cpp-1)-1)+1,上面的cpp还是指向c+1的位置,只有前置加加和前置减减会改变数值,所以cpp-1就指向c+2,解引用后再减1,就变成了c+1,那么这个代码就变成了*(c+1)+1,然后再解引用,得到NEW首元素的地址,最后+1,打印的就是EW.
好了,以上就是目前我认为非常经典的一些关于指针的题型,我希望大家一定要好好理解这几道题,特别是最后一道题,一定要根据运行图去看代码,去看我的解读,不然很可能会搞不懂!!
看到这里的友友能不能给我一个点赞呢?你们的点赞就是对我最好的支持,如果上面的题中出现了部分错误,还请见谅啦,我还是会继续努力的,谢谢大家,我们下周见!
对了,最近有铁铁问我怎么学习编程语言,如果大家想要系统性学习编程语言而找不到合适的课程的话,也可以私聊小编哦,小编可以推荐一些小编认为比较好的课程哦。