前言:Hello!我是@每天都要敲代码。上两章我们一起学习了指针:字符指针的使用、指针数组和数组指针的理解、数组和指针的传参、函数指针、函数指针数组、回调函数、指向函数指针数组的指针、利用普通法、函数指针数组法、回调函数法实现计算器、利用回调函数模拟实现qsort。没有掌握的要先去学习过后再来做题:《指针进阶1》《指针进阶2》;这章我们将继续学习指针的内容,找一些习题,巩固所学知识!
1. 小试牛刀
在讲解之前我们还是要回顾一下两个知识点:
1、sizeof(数组名)--数组名表示整个数组---计算的是整个数组的大小
2、&数组名---数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素的地址
1.1 一维数组
1、sizeof(a)操作的是整个数组;总共4个元素,每个元素都是int类型,占4个字节;所以最终结果是:4*4 = 16;
2、除了sizeof(a)和&a,代表操作的是整个数组,除此之外,所有的数组名都是数组首元素的地址:
sizof(a+0),a代表首元素的地址,a+0代表第一个元素的地址;最终sizeof(a+0)就是计算首元素地址的大小,是4或者8,对应着编译器是32 位和64位;
3、sizeof(*a),a代表的是首元素的地址,(*a)表示对首元素的地址进行解应用,访问的就是第一个元素1(是整型);sizeof(1)就是4;
4、sizeof(a+1),a代表的是首元素的地址,a+1代表的是第2个元素的地址;最终就是4或者8;
5、sizeof(a[1]),a[1]代表的是第2个元素2;sizoe(2)就是4。
6、sizeof(&a),&a代表的是整个数组的地址;但只要是地址,无论是什么类型,谁的地址,只要是地址,结果就是4或者8;
7、sizeof(*&a),*&a我们可以理解为先取地址,然后在解应用,抵消了;所以*&a就等价a;结果就是sizeof(a):16;
8、sizeof(&a+1),&a代表整个数组的二地址,&a+1代表跳过这个数组,下一个数组首元素的地址;是地址,结果就是4或者8;
9、sizeof(&a[0]),a[0]代表第一个元素,&a[0]代表一个元素的地址,是地址,结果就是4或者8;
10、sizeof(&a[0]+1),&a[0]是第一个元素的地址(也就是首元素的地址);&a[0]+1代表的就是第二个元素的地址,是地址,结果就是4或者8。
我用的是32位编译器,所以对于地址,就是4;如果用64位编译器,就是8了:
1.2 字符数组
对于字符数组计算长度,我们要先弄清楚下面几个概念:
(1)对于字符数组,我们常见的有2两种形式,一种是由单个字符组成的数组,另一种是由字符串组成的数组;
(2)这两种形式有一个本质的区别:单个字符组成的数组,默认没有\0;字符串组成的数组,默认有\0;
(3)并且对于求字符串长度,既可以用sizeof也可以用strlen,szieof计算长度会默认包括\0,strlen计算长度是找到\0就终止,不会包括\0;
(4)对于整型数组,我们一般用sizeof计算长度;对于字符串,我们一般用strlen计算长度
注意:sizeof后面既能跟地址,也能跟具体元素;strlen 后面只能跟地址!
1.2.1 用sizeof和strlen计算由单个字符组成的数组
1、用sizeof来计算由单个字符组成的数组
1、sizeof(arr),由单个字符组成的数组,默认没有\0;有6个元素,每个元素都char类型占1个字节,最终结果就是:6*1 = 6;
2、sizeof(arr+0),arr代表首元素的地址,arr+0表示第一个元素的地址;只要是地址无论是char类型还是int类型,结果都是4或者8;
3、sizeof(*arr),arr代表首元素的地址,*arr表示第一个元素‘a’;占1个字节,结果就是1;
4、sizeof(arr[1]),arr[1]代表第二个元素‘b’;占1个字节,结果就是1;
5、sizeof(&arr),&arr代表的整个数组的地址,只要是地址,结果就是4或者8;
6、sizeof(&arr+1),&arr代表的整个数组的地址,&arr+1表示跳过一个数组,下一个数组首元素的地址;只要是地址,结果就是4或者8;
7、sizeof(&arr[0]+1),&arr[0]代表第一个元素的地址,&arr[0]+1代表第二个元素的地址;只要是地址,结果就是4或者8;
8、sizeof(*&arr),&arr表示整个元素的地址,*&arr表示对整个数组解应用,得到的就是一整个数组;所以*&arr就等价于arr,sizeof(arr)结果就是6。
我用的是32位编译器,所以对于地址,就是4;如果用64位编译器,就是8了:
2、用strlen来计算由单个字符组成的数组
strlen找到\0才能停下来,不然就会一直往后找;strlen后面只能跟地址:strlen(地址)
1、strlen(arr),根据首元素的地址,一直往后找\0;因为由单个字符组成的字符串默认没有\0,所以结果就是随机值;
2、strlen(arr+0),arr是首元素的地址,arr+0就是第一个元素的地址,也就是首元素的地址;和第一个结果一样,是随机值;
3、strlen(*arr),arr代表首元素的地址,*arr代表取到第一个元素‘a’;strlen后面跟具体元素,是错误的写法error;
4、strlen(arr[1]),arr[1]代表取到第二个元素'b';strlen后面跟具体元素,是错误的写法error;
5、strlen(&arr),&arr表示整个数组的地址,其实也就等于首元素的地址;一直往后面找\0,是随机值;
6、strlen(&arr + 1),&arr代表首元素的地址,&arr+1代表跳过整个数组,下一个数组首元素的地址,从下一个数组首地址开始;最终结果也就是:随机值-6;
7、strlen(&arr[0] + 1),&arr[0]代表第一个元素的地址,&arr[0]+1代表第二个元素的地址;从第二个元素的地址开始;最终结果也就是:随机值-1。
把3、4屏蔽掉结果就是随机值19,随机值19,随机值19,随机值-6,随机值-1:
1.2.2 用sizeof和strlen计算由字符串组成的数组
1、用sizeof来计算字符串组成的数组
1、sizeof(arr),由字符串组成的数组,默认有\0;总共有7个元素(6个元素+1个\0);每个元素都char类型占1个字节,最终结果就是:7*1 = 7;
2、sizeof(arr+0),arr代表首元素的地址,arr+0就是第一个元素的地址,也就是第一个元素的地址;只要是地址,结果就是4或者8;
3、sizeof(*arr),arr代表首元素的地址,*arr就是取出首元素'a';是char类型,占1个字节,最终结果就是:1
4、sizeof(arr[1]),ar[1]代表取出的是第二个元素‘b’;是char类型,占1个字节,最终结果就是:1
5、sizeof(&arr),&arr代表是整个数组的地址;只要是地址,结果就是4或者8;
6、sizeof(&arr+1),&arr代表是整个数组的地址;&arr+1表示跳过一个数组,下一个数组的首元素地址;只要是地址,结果就是4或者8;
7、sizeof(&arr[0]+1),&arr[0]代表第一个元素的地址;&arr[0]+1代表第二个元素的地址;只要是地址,结果就是4或者8;
我用的是32位编译器,所以对于地址,就是4;如果用64位编译器,就是8了:
2、用strlen来计算字符串组成的数组
1、strlen(arr),strlen是找\0,而字符串刚好默认里面有\0,但是不算\0的大小;总共有6个元素;每个元素都char类型占1个字节,最终结果就是:6*1 = 6;
2、strlen(arr+0),arr代表是首元素的地址,arr+0代表第一个元素的地址(也就是首元素的地址);所以和上面一样,最终结果就是:6*1 = 6;
3、strlen(*arr),arr代表首元素的地址,*arr表示取第一个元素;strlen后面不能跟具体元素,只能跟具体地址;所以是错误写法error;
4、strlen(arr[1]),arr[1]是取第二个元素,strlen后面跟具体元素,错误写法error;
5、strlen(&arr),&arr代表整个数组的地址,也就等于首元素的地址;总共有6个元素;每个元素都char类型占1个字节,最终结果就是:6*1 = 6;
6、strlen(&arr + 1),&arr代表整个数组的地址,*arr+1表示跳过这个数组,下一个数组的首地址;但是下一个地址几个元素?有没有\0?我们都不清楚;所以是随机值;
7、strlen(&arr[0] + 1),&arr[0]代表第一个元素的地址;&arr[0] + 1表示第二个元素的的地址,从第二个元素开始;所以最终结果就是6-1 = 5;
把3、4屏蔽掉结果就是6、6、6、随机值16、5
1.2.3 用sizeof和strlen计算由指针指向的常量字符串
1、用sizeof计算由指针指向的常量字符串
1、sizeof(p),p是一个指针;指向的是首元素的地址,只要是地址,结果就是4或者8;
2、sizeof(p+1),p指针+1,指向的就是第二个元素的地址,只要是地址,结果就是4或者8;
3、sizeof(*p),p是一个指针;指向的是首元素的地址;*p表示取出第一个字符a,占1个字节,最终结果就是:1;
4、sizeof(p[0]),p[0]代表取出第一个字符a,占1个字节,最终结果就是:1;
5、sizeof(&p),p是一个指针,&p表示取出p的地址;指针p的地址也是一个地址;只要是地址,结果就是4或者8;
6、sizeof(&p + 1),&p表示取出p的地址,&p+1就是跳过一个p的地址,本质还是地址;只要是地址,结果就是4或者8;
7、sizeof(&p[0] + 1),&p[0]表示第一个元素a的地址,&p[0] + 1就是第二个元素b的地址;只要是地址,结果就是4或者8;
我用的是32位编译器,所以对于地址,就是4;如果用64位编译器,就是8了:
2、用strlen计算由指针指向的常量字符串
1、strlen(p),p指向的是首元素a的地址,strlen往后面找\0,最终结果就是:6;
2、strlen(p + 1),指针p+1表示从第二个元素地址开始,往后面找\0,最终结果就是:6-1 = 5;
3、strlen(*p),p指向的是首元素a的地址,*p解应用找到首元素a;strlen后面跟具体元素是错误写法error;
4、strlen(p[0]),p[0]代表是第一个元素a;strlen后面跟具体元素是错误写法error;
5、strlen(&p),&p表示取p的地址,p本身是地址占4个字节;但是我们并不知道p占用的这4个字节放的什么,本身有没有\0;所以最终是随机值;
6、strlen(&p + 1),&p表示取出p的地址,&p+1表示跳过一个p的内容;此时往后找\0,但是&p+1后面我们还是不知道内存的存储情况;所以还是随机值
7、strlen(&p[0] + 1),&p[0]第一个元素的地址,&p[0] + 1表示b的地址;从b开始往后数,结果就是:5;
把3、4屏蔽掉结果就是6、5、随机值3、随机值11、5
1.3 二维数组
首先我们对二维数组要有一个简单的理解:
1、二维数数组的数组名也代表首元素的地址,但是首元素的地址实际上是第一行的地址;
2、二维数组可以看成几个一维数组组成,比如:arr[0]对于二维数组而言,可以理解为第一行元素的数组名;也就相当于一个一维数组的数组名!
1、sizeof(a),数组名单独放在sizeof里面,求的是整个数组的大小;总共3*4 = 12个元素,每个元素是整型占4个字节;最终大小就是:12*4 = 48;
2、sizeof(a[0][0]),a[0][0]表示的是二维数组的一个元素;最终求的是第一个元素的大小,是4;
3、sizeof(a[0]),a[0]可以理解为第一行元素的数组名,实际上也就相当于一个一维数组的数组名;数组名单独放到sizeof里面,求的是整个数组的大小;也就是4*4 = 16;
4、sizeof(a[0]+1),a[0]为第一行的数组名,并没有单独放到sizeof,表示的是首元素的地址;a[0]+1表示的是第一行第二个元素的地址;只要是地址,结果就是4或者8;
5、sizeof(*(a[0] + 1)),就是步骤4的解应用,拿到的第一行第二个元素,结果是4;
6、sizeof(a+1),数组名a没有单独放到sizeof里面,表示的是首元素的地址:实际上也就是第一行的地址;a+1就是二维数组第二行的地址;只要是地址,结果就是4或者8;
7、sizeof(*(a+1)),就是步骤6的解应用,拿到的是第二行的所有元素;也就是4*4 = 16;
8、sizeof(&a[0] + 1),a[0]是第一行的数组名,&a[0]取出的就是第一行的地址;&a[0]+1就是第二行的地址;只要是地址,结果就是4或者8;
9、sizeof(*(&a[0] + 1)),就是步骤8的解应用,拿到的是第二行的所有元素;也就是4*4 = 16;
10、sizeof(*a),数组名没有单独放到sizeof里面,代表的是第一行的地址;解应用拿到的是第一行的所有元素;也就是4*4 = 16;
11、sizeof(a[3]);看着好像是越界了;但是这里我们要明白一点:值属性和类型属性
比如3+5表达式:1.值属性是8 2.类型属性是int(4个字节)
并且sizeof()内部的表达式是不计算的,它并不会计算里面的值属性;只会计算类型属性!
解释:a[3]其实是第四行的数组名(如果有的话),但是其实不存在,也能通过类型计算大小的是16;这里如果计算下标为负值也是16,例如:sizeof(a[-1]) = 16
2. 指针笔试题
笔试题 1:
我们先理解一下题目,然后在画图进行分析:
(1)&a代表是整个数组的地址,&a+1表示跳过整个数组,下一个数组的首地址。
(2)所以*(a+1)就等价于a[1] = 2这个很简单;
(3)int* ptr = (int*)(&a + 1)表示指针ptr指向的是下一个数组的首元素地址,(ptr-1)表示指针后移一位指向5的地址,一解应用结果就是元素5。
逻辑图:
测试结果:
笔试题 2 :
这里已经给出了结构体的大小,具体是怎么算的,等我们复习到会深度讲解的!
这里我们首先要明白一点:指针的类型决定了它的步长:
1、如果是整型指针int* p,那么p+1就是跳过4个字节;
2、如果是浮点型指针char* p,那么p+1就是跳过1个字节;
3、如果是结构体型指针struct Test* p,那么p+1就是跳过一个结构体的长度,这里就是跳过20个字节!
对于1:p+0x1,指针p+1跳过的是一个结构体类型所占的字节20,20转化为十六进制也就是14;所以最终的结果就是:p的地址+0x14;
对于2:把p强制类型转换为长整型int,+1就是加1;就相当于一个整型数据4,+1就是5;整型+整型;所以最终的结果就是:p的地址+0x1;
对于3:把p强制类型转换为整型指针int*,指针p+1跳过的就是4个字节;所以最终的结果就是:p的地址+0x4;
测试结果:
笔试题 3:
这道题就是专门区分:指针+1 和 整型+1的区别!对于指针+1,根据指针的类型来决定跳过几个字节。对于整型+1,+1就是跳过一个字节!
第一个问题:&a+1表示跳过一整个数组的下个数组的首元素地址,并把这个地址赋值给ptr1;ptr[-1]就等价于*(ptr-1);下面通过画图分析一下:
逻辑图:
第二个问题:数组a是首元素的地址,强制类型转换为一个整型,整型+1就是+1,跳过一个字节;而一个整型是占4个字节,所以我们就需要更细致的画图方式:
测试结果: