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

相关文章
|
1月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
27 0
|
2月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
109 16
|
2月前
|
算法 C语言
【C语言程序设计——循环程序设计】求解最大公约数(头歌实践教学平台习题)【合集】
采用欧几里得算法(EuclideanAlgorithm)求解两个正整数的最大公约数。的最大公约数,然后检查最大公约数是否大于1。如果是,就返回1,表示。根据提示,在右侧编辑器Begin--End之间的区域内补充必要的代码。作为新的参数传递进去。这个递归过程会不断进行,直到。有除1以外的公约数;变为0,此时就找到了最大公约数。开始你的任务吧,祝你成功!是否为0,如果是,那么。就是最大公约数,直接返回。
118 18
|
2月前
|
Serverless C语言
【C语言程序设计——循环程序设计】利用循环求数值 x 的平方根(头歌实践教学平台习题)【合集】
根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码,求解出数值x的平方根;运用迭代公式,编写一个循环程序,求解出数值x的平方根。注意:不能直接用平方根公式/函数求解本题!开始你的任务吧,祝你成功!​ 相关知识 求平方根的迭代公式 绝对值函数fabs() 循环语句 一、求平方根的迭代公式 1.原理 在C语言中,求一个数的平方根可以使用牛顿迭代法。对于方程(为要求平方根的数),设是的第n次近似值,牛顿迭代公式为。 其基本思想是从一个初始近似值开始,通过不断迭代这个公式,使得越来越接近。
80 18
|
2月前
|
C语言
【C语言程序设计——循环程序设计】统计海军鸣放礼炮声数量(头歌实践教学平台习题)【合集】
有A、B、C三艘军舰同时开始鸣放礼炮各21响。已知A舰每隔5秒1次,B舰每隔6秒放1次,C舰每隔7秒放1次。编程计算观众总共听到几次礼炮声。根据提示,在右侧编辑器Begin--End之间的区域内补充必要的代码。开始你的任务吧,祝你成功!
83 13
|
2月前
|
存储 安全 C语言
【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
分支的语句,这可能不是预期的行为,这种现象被称为“case穿透”,在某些特定情况下可以利用这一特性来简化代码,但在大多数情况下,需要谨慎使用。编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。根据提示,在右侧编辑器补充代码,计算并输出最终预测的身高。分支下的语句,提示用户输入无效。常量的值必须是唯一的,且在同一个。语句的作用至关重要,如果遗漏。开始你的任务吧,祝你成功!,程序将会继续执行下一个。常量都不匹配,就会执行。来确保程序的正确性。
131 10
|
2月前
|
小程序 C语言
【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】
目录 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果: 任务描述 相关知识 编程编写一个程序,从键盘输入3个变量的值,例如a=5,b=6,c=7,然后将3个变量的值进行交换,使得a=6,b=7,c=5。面积=sqrt(s(s−a)(s−b)(s−c)),s=(a+b+c)/2。使用输入函数获取半径,格式指示符与数据类型一致,实验一下,不一致会如何。根据提示,在右侧编辑器补充代码,计算并输出圆的周长和面积。
74 10
|
2月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
61 3
|
2月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
41 2
|
2月前
|
存储 C语言
【C语言程序设计——循环程序设计】利用数列的累加和求 sinx(头歌实践教学平台习题)【合集】
项的累加和,一般会使用循环结构,在每次循环中计算出当前项的值(可能基于通项公式或者递推关系),然后累加到一个用于存储累加和的变量中。在C语言中推导数列中的某一项,通常需要依据数列给定的通项公式或者前后项之间的递推关系来实现。例如,对于一个简单的等差数列,其通项公式为。的级数,其每一项之间存在特定的递推关系(后项的分子是其前项的分子乘上。,计算sinx的值,直到最后一项的绝对值小于。为项数),就可以通过代码来计算出指定项的值。对于更复杂的数列,像题目中涉及的用于近似计算。开始你的任务吧,祝你成功!
80 6