C语言进阶第四篇【指针习题详解】(上)

简介: C语言进阶第四篇【指针习题详解】(上)

前言:Hello!我是@每天都要敲代码。上两章我们一起学习了指针:字符指针的使用、指针数组和数组指针的理解、数组和指针的传参、函数指针、函数指针数组、回调函数、指向函数指针数组的指针、利用普通法、函数指针数组法、回调函数法实现计算器、利用回调函数模拟实现qsort。没有掌握的要先去学习过后再来做题:《指针进阶1》《指针进阶2》;这章我们将继续学习指针的内容,找一些习题,巩固所学知识!


1. 小试牛刀

在讲解之前我们还是要回顾一下两个知识点:


1、sizeof(数组名)--数组名表示整个数组---计算的是整个数组的大小

2、&数组名---数组名表示整个数组,取出的是整个数组的地址

除此之外,所有的数组名都是数组首元素的地址


1.1 一维数组

755bfc10c2194286b3c92393e9ca8211.png

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了:

24b7e5c743f748b48f420e58c467b525.png


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计算由单个字符组成的数组

a29f7ed7da53463a905fe802edc16bea.png

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了:

fff79ab90171446aab1348b9d3d7004f.png


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:

1a2b1c89d78b47298c4f0eb97301d707.png


1.2.2 用sizeof和strlen计算由字符串组成的数组

97440daa2ce041eeaa19f6acf3c286bf.png

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了:

36e05a6f7a7143c98bf518d9eca0aa4a.png


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

4559c9e0977f49ee9a919395dc2d323b.png


1.2.3 用sizeof和strlen计算由指针指向的常量字符串

d37bfcb1c02a47b1ab0530df6dee2683.png

779cecf31d6747f7bcc0d3ed9d4e9272.png


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了:


470c2f7d5ebb4265b6ca699f38fac153.png


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;所以最终是随机值;


a83b5701fa7848638ae88779c5da239b.png

6、strlen(&p + 1),&p表示取出p的地址,&p+1表示跳过一个p的内容;此时往后找\0,但是&p+1后面我们还是不知道内存的存储情况;所以还是随机值

12353eb393b24931900aac8a43cac6ad.png


7、strlen(&p[0] + 1),&p[0]第一个元素的地址,&p[0] + 1表示b的地址;从b开始往后数,结果就是:5;


把3、4屏蔽掉结果就是6、5、随机值3、随机值11、5

715dbd97da974c0682e3182940700f59.png


1.3 二维数组


首先我们对二维数组要有一个简单的理解:


1、二维数数组的数组名也代表首元素的地址,但是首元素的地址实际上是第一行的地址;


2、二维数组可以看成几个一维数组组成,比如:arr[0]对于二维数组而言,可以理解为第一行元素的数组名;也就相当于一个一维数组的数组名!


30a3d2f8219149c2b0e344d8724dae5f.png

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:

image.png

我们先理解一下题目,然后在画图进行分析:


(1)&a代表是整个数组的地址,&a+1表示跳过整个数组,下一个数组的首地址。


(2)所以*(a+1)就等价于a[1] = 2这个很简单;


(3)int* ptr = (int*)(&a + 1)表示指针ptr指向的是下一个数组的首元素地址,(ptr-1)表示指针后移一位指向5的地址,一解应用结果就是元素5。


逻辑图:


78e9566604ee4d4586c60509773ca760.png

测试结果:

image.png


笔试题 2 :

2d42f8fa604f407280ad22eb41916d8c.png

这里已经给出了结构体的大小,具体是怎么算的,等我们复习到会深度讲解的!


这里我们首先要明白一点:指针的类型决定了它的步长:


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;


测试结果:

c8bf7490321044c3a4f03d430f845740.png


笔试题 3:

image.png

这道题就是专门区分:指针+1  和  整型+1的区别!对于指针+1,根据指针的类型来决定跳过几个字节。对于整型+1,+1就是跳过一个字节!


第一个问题:&a+1表示跳过一整个数组的下个数组的首元素地址,并把这个地址赋值给ptr1;ptr[-1]就等价于*(ptr-1);下面通过画图分析一下:


逻辑图:

1edc8ebb44074137bc175d9cc288528c.png


第二个问题:数组a是首元素的地址,强制类型转换为一个整型,整型+1就是+1,跳过一个字节;而一个整型是占4个字节,所以我们就需要更细致的画图方式:

bc4f3c4d83c1495bb281927e04e8c377.png


测试结果:

e530f68e44134e35820256919b5caca6.png

相关文章
|
16天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
19天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
24天前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
1月前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
1月前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
1月前
|
机器学习/深度学习 C语言
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
要保持最小的步数,每一次汉诺塔问题(无论是最初还是递归过程中的),如果此时初始柱盘子数为偶数,我们第一步是把最上面的盘子移动到中转柱,如果为奇数,我们第一步则是将其移动到目标柱。
【C语言篇】递归详细介绍(基础概念习题及汉诺塔等进阶问题)
|
1月前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
1月前
|
C语言
【C语言】指针速览
【C语言】指针速览
17 0
|
1月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
1月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)