不允许你还不了解指针的那些事(二)(从入门到精通看这一篇就够了)(数组传参的本质+冒泡排序+数组指针+指针数组)

目录

数组名的理解

使用指针访问数组

一维数组传参的本质

冒泡排序

二级指针

指针数组

指针数组模拟二维数组

字符指针变量

数组指针变量

二维数组传参的本质

函数指针变量

函数指针变量的创建 

函数指针变量的使用

两段有趣的代码

代码一

代码二

typedef关键字 

函数指针数组

转移表


个人专栏:《零基础学C语言》

附赠:《数据结构世界》


不要划走!不要划走!这篇博客真的写了很久很久,呕心沥血,干货满满 ,能不能点个赞或者说一句鼓励的话来支持一下博主?

数组名的理解

先来看一段代码 

我们发现数组名和数组首元素的地址打印出的结果⼀模⼀样,数组名就是数组首元素(第⼀个元素)的地址 

但是下面这段代码怎么解释呢? 

 

如果arr代表首元素地址,那计算结果应该是4才对啊? 

其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有 两个例外
sizeof(数组名) ,sizeof中单独放数组名,这里的数组名表示整个数组, 计算的是整个数组的大小 ,单位是字节
&数组名 ,这里的数组名表示整个数组, 取出的是整个数组的地址 (整个数组的地址和数组首元素的地址是有区别的)

这里还看不出arr和&arr的区别,请看以下代码

这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是首元素的地址, +1就是跳过一个元素
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址, +1 操作是跳过整个数组 的。

使用指针访问数组

在这里数组名arr和指针p其实是等价的 

下列四种等价写法 

其实编译器在计算arr[i]时,就会把它转换为 *(arr+i),再进行计算  

所以下面展示一种奇特的写法

i[arr] <----> *(i+arr) <----> *(arr+i) <----> arr[i]  

一维数组传参的本质

首先从一个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给一个函数后,函数内部求数组的元素个数吗? 

这里为什么是1呢?因为前面学过,函数传参arr,数组名是首元素的地址 。函数形参arr实际上是一个整型指针,而x86环境下,其大小为4个字节,所以除以一个整型元素等于1 

那么再来看看这段代码,想想输出结果是什么呢?

形参arr是字符指针,还是4个字节,但是它一次只能访问1个字节,所以相除结果为4  

所以在函数内部,此时arr数组和指针没有区别,相互等价  

当然,上面这种写法还有缺陷,因为只能打印固定元素,一旦原数组改变,就没办法完整打印。所以,我们最好算好元素个数,传入函数。  

为什么传入函数就变成指针了呢?从C语言设计的角度考虑, 因为通过指针已经能访问整个数组,而且如果将整个数组都传入函数空间开销是非常大的,会造成空间浪费 

冒泡排序

 冒泡排序核心思想就是:两两相邻的元素进行比较 

先写一个基本框架  

再实现函数定义部分 ,先外层循环确定趟数,再内层循环确定每趟交换的对数,最后判断相邻元素大小,如果不满足顺序就交换 

这样就实现了冒泡排序。但是上述代码还可以再进行优化,试想一下,如果要排序的数组是

9,0,1,2,3,4,5,6,7,8  我们第一趟排序完便已经升序了 ,但是还在不停的循环判断。所以,我们可以这样改。

加入flag变量,表示数组当前是否有序。而判断有序的方法,则是如果一趟冒泡排序下来,没有一对交换,则证明有序。 反之,如果有交换,则flag置为0,表示无序,则继续下一趟冒泡排序。这样,就可以节省时间。 

二级指针

指针变量也是变量,是变量就有地址,那 指针变量的地址存放 在哪里?
这就是 二级指针

a的地址取出,放在一级指针p中;把p的地址取出,放在二级指针pp中 。二级指针类型有两个**,比如int**,前面的int*说明pp指向的对象类型,后面的*说明pp是指针变量 

*pp通过p的地址找到p,*(*pp)再对p解引用,通过p中存储a的地址找到a  

依此类推,***就是三级指针……,不过三级指针及以上就用得很少了  

指针数组

指针数组是 指针还是数组
我们类比一下,整型数组,是存放整型的数组,字符数组是存放字符的数组。
那指针数组呢?是 存放指针的数组

我们先来做一下类比

那么,希望有一个数组,有5个元素,每个元素是整型指针,应该怎么写呢? 

应该怎么理解呢?arr先与[ ]结合为数组,有5个元素 ,每个元素是int*(整形指针类型)。指针数组的每个元素是地址,又可以指向一块区域 

指针数组模拟二维数组

那可能有同学会疑惑,这个指针数组有什么用呢?下面我们来演示用指针数组模拟二维数组 

存储了3个元素的指针数组每一个元素就是一个指针指向对应数组首元素的地址(数组名的理解)  

 

arr[ i ][ j ] ---->*( *(arr+i) + j ),两种等价写法 

字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char* 

这里是把一个字符串放到pstr指针变量里了吗? 

不是把字符串abcdef\0存放在p中,而是把第一个字符的地址存放在p中  

1. 你可以把字符串想象为一个字符数组,但是这个数组是不能修改的
2. 当常量字符串出现在表达式中的时候,它的值是第一个字符的地址  

那我们就可以来看看一些奇特的写法 

数组名,一般就是首元素地址,那么这里常量字符串和字符指针p都存储的是第一个字符的地址,那么也能用数组的方式进行打印访问。  

 但因为常量字符串是不能修改的,所以最好在p前用const进行修饰 

 我们再来看一道有趣的题目,请分析打印的结果:

有的同学可能会惊讶,这是为什么呢?因为,str1和str2是两个数组,因此有不同的地址,而str3和str4都是字符指针,指向相同的常量字符串 ,根据C语言的规则,相同的常量字符串只会保存一份(为了节省内存空间) 

数组指针变量

之前我们学习了指针数组,指针数组是一种数组,数组中存放的是地址(指针)。
数组指针变量是 指针变量?还是数组?
答案是: 指针变量

 

我们已经熟悉:
整形指针变量: int * pi; 存放的是 整形变量 地址 ,能够指向整形数据的指针。
浮点型指针变量: float * pf; 存放 浮点型变量 地址 ,能够指向浮点型数据的指针。
数组指针变量 应该是:存放的应该是 数组的地址 ,能够指向数组的指针变量。

那我们来判断一下,下面的两段代码分别代表什么?

解释:p 先和*结合 ,说明p是⼀个 指针变量 ,然后指着指向的是一个大小为10个整型的数组。所以
p是一个指针,指向一个数组,叫 数组指针
注意: []的优先级要高于*号的,所以必须 加上()来保证p先和*结合

那有同学就会问了,数组指针变量怎么初始化?其实很简单,数组指针中存放的是整个数组的地址,那么只要&arr将数组的地址取出,放入数组指针中即可 

 这里再对比一下,普通整型指针都是存放数组arr首元素的地址+1跳过一个元素;而数组指针是存放数组arr整体的地址+1跳过整个数组 

 

二维数组传参的本质

有了数组指针的理解,我们就能够讲一下二维数组传参的本质了。 

让我们继续类比,过去我们讨论一维数组传参本质, 形参可以是数组,也可以是指针

为什么呢?

1.写成数组,更加直观,为了方便理解

2.写成指针,是因为数组传参,传过去的是数组首元素的地址

在之前扫雷项目的实现中,我们已经用过了二维数组传参,当时写的是数组的形式。所以,二维数组传参,写成数组是可以的,更加直观,方便理解,但是能写成指针的形式吗? 

可以的!二维数组,其实是元素为一维数组的数组。对于二维数组,首元素是第一行,首元素的地址,就是第一行的地址。那么根据数组名的理解,二维数组数组名就代表第一行的地址。 

二维数组传参本质 上也是传递了地址, 传递的是第一行这个一维数组的地址

函数指针变量

什么是函数指针变量呢?
根据前面学习整型指针,数组指针的时候,我们的 类比关系 ,我们不难得出结论:
函数指针变量 应该是用来 存放函数地址的 ,未来通过地址能够调用函数的。

但是,数组名和函数名还是有所不同的 ,我们发现数组名是首元素的地址,&数组名才是整个数组的地址;但是函数名和&函数名都是函数的地址 

 

函数指针变量的创建 

那么,函数指针应该怎样表示呢?我们来类比一下: 

解释:*先与pf结合,表示它是一个指针变量,后面跟()表示函数调用,括号内表示函数参数,最左边表示函数返回类型  

再举一个例子

 

函数指针变量的使用

那函数指针怎么使用呢? 

 

我们平时调用函数,写的都是ret的形式,那么函数指针就可以替换函数名的部分,先对指针解引用,后面在输入参数  

 前面说过,函数名和&函数名,都是函数的地址。那么,在创建函数指针的时候,右侧可以不写&。同时,函数指针代表的也是函数地址,那么也可以不写*。 

上述四种写法都是等价的  

两段有趣的代码

请大家尝试思考一下下面两段代码表达的是什么意思?

代码一

首先,void(*)()是刚刚学过的函数指针类型参数为空,返回类型为void

其次,0前面的括号,表示强制类型转换,就比如  (int)3.14

最后,外层的  (*)( ),是一次对函数指针的调用,参数为空

 综上,这段代码是一次函数调用。先将0(数值)强制类型转换成函数指针类型(地址),再对它进行调用 

代码二

这段代码是一次函数声明signal是函数名。 

signal参数有两个,第一个是整型(int),第二个是函数指针类型,该指针指向的函数参数为int,返回类型为void  

signal返回类型,也是void (*)(int)函数指针类型,该指针指向的函数参数为int,返回类型为void  

typedef关键字 

是不是感觉上述函数声明太抽象,那我们就可以使用typedef关键字进行重定义

typedef 是用来类型重命名的,可以将复杂的类型,简单化

注意 : 数组指针和函数指针类型重定义时,重新定义的函数名要写在内部,不能写在最右侧 

这样,该代码是不是就好理解很多了?  

 

两段代码均出自:《C陷阱和缺陷》这本书  

函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,

那要把函数的地址存到一个数组中,
那这个数组就叫 函数指针数组 ,那函数指针的数组如何定义呢?

parr1 先和 [] 结合 ,说明 parr1是数组,数组的内容是什么呢?
int (*)() 类型的 函数指针

 

函数指针数组小小的运用 

数组中每个地址都指向一个函数 

依此类推,那么数组指针数组又怎么表示呢?比如:int(*parr1[ 3 ])[ 3 ] 

上述例子,表示parr1是数组,数组的每个元素是int (*) [3]类型的数组指针,每个指针指向存储3个int(整型)的数组

转移表

前面的运用其实并不是函数指针数组的真正用途,下面来介绍它真正的好处。

函数指针数组 的用途: 转移表
举例:计算器的一般实现:

 我们想写一个加减乘除的计算器,先写一个菜单

主体框架用do-while循环和switch语句构建

紧接着是每条switch语句代表一种算法(加、减、乘、除) ,但是我们发现相似的代码出现了多份,显得有些冗余,这应该怎么办呢?

此时函数指针数组就派上用场了!这里的函数指针数组,我们称为转移表。这样,代码就简洁了不少,相同的代码只出现一份。

看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注

💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/156091.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

反向运算放大器

在学习模拟电路的时候&#xff0c;学习到运算放大器&#xff0c;但实际印象并不深刻&#xff0c;在此进行二次知识整理&#xff0c;以加深深度&#xff0c;下面是我个人对该器件的理解&#xff0c;其他知识暂时不深究&#xff0c;只说一下怎么用。 1、反向运算放大器干什么的&…

有能一键批量转换,轻松将PDF、图片转为Word/Excel的软件吗?

随着数字化时代的到来&#xff0c;OCR技术在我们的生活中变得越来越重要。无论是从图片中提取文字&#xff0c;还是将PDF、图片格式的文件转换为Word或Excel格式&#xff0c;OCR软件都能够为我们提供极大的便利。然而&#xff0c;市面上的OCR软件种类繁多&#xff0c;哪一款软件…

obsidian和bookmaster

1 手动安装插件 插件地址&#xff1a;https://forum-zh.obsidian.md/t/topic/12333 安装file服务器 地址&#xff1a;http://www.rejetto.com/hfs/ hfs.exe可以改个端口 改成8866&#xff0c;ip地址也可以改成 127.0.0.1 # 因为安装到本地 如果要创建账户的话&#xff0c;就…

Google codelab WebGPU入门教程源码<7> - 完整的元胞自动机之生命游戏的完整实现(源码)

对应的教程文章: https://codelabs.developers.google.com/your-first-webgpu-app?hlzh-cn#7 对应的源码执行效果: 对应的教程源码: 此处源码和教程本身提供的部分代码可能存在一点差异。 class Color4 {r: number;g: number;b: number;a: number;constructor(pr 1.0, …

小白也想写综述(一)

前言 在选择科研方向时&#xff0c;考虑自己的兴趣和职业目标是非常重要的&#xff1a; 综述论文的价值&#xff1a;撰写综述论文&#xff0c;尤其是在深度强化学习和区块链这样的前沿技术领域&#xff0c;能够帮助建立扎实的理论基础&#xff0c;并且对整个领域有一个全面的认…

【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷4

单选题 1、Soda is sold in packs of 6、12 and 24 cans.What is the minimum number of packs needed to buy exactly 90cans of soda? A、4 B、5 C、6 D、8 答案&#xff1a;B 2、蓝桥杯STEMA考试中心发送有同样内容、重量、体积的试卷&#xff0c;从北京10箱和上海6箱…

Reflect 对象的创建目的

目录 前言 逻辑Reflect对象的创建目的包括&#xff1a; 代码示例 使用 Reflect 操作属性 使用 Reflect 检查属性是否存在 使用 Reflect 创建代理 用法 Reflect对象的用法包括&#xff1a; 用于读取和设置对象属性的方法&#xff1a;Reflect.get(obj, prop)、Reflect.s…

等级保护建设全流程

等保&#xff0c;全称为信息安全等级保护&#xff0c;是对信息和信息载体按照重要性等级分级进行保护的一种工作。 企业的信息系统有收集、储存用户信息的&#xff0c;都需要进行等保建设&#xff0c;以此来整改提升系统的安全防护能力&#xff0c;降低被攻击的风险。若不然一旦…

通付盾Web3专题 | KYT/AML:Web3合规展业的必要条件

与传统证券一样&#xff0c;基于区块链技术发展出来的虚拟资产交易所经历了快速发展而缺乏有效监管的行业早期。除了科技光环加持的各种区块链项目方、造富神话之外&#xff0c;交易所遭到黑客攻击、内部偷窃作恶、甚至经营主体异常而致使投资人血本无归的案例亦令人触目惊心。…

PostgreSQL技术大讲堂 - 第34讲:调优工具pgBagder部署

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第34讲&#…

Consumer的负载均衡

想要提高Consumer的处理速度&#xff0c;可以启动多个Consumer并发处理&#xff0c;这个时候就涉及如何在多个Consumer之间负载均衡的问题&#xff0c;接下来结合源码分析Consumer的负载均衡实现。 要做负载均衡&#xff0c;必须知道一些全局信息&#xff0c;也就是一个Consum…

Accelerate 0.24.0文档 四:Megatron-LM

参考《Megatron-LM》 文章目录 一、Megatron-LM集成简介二、环境配置设置conda环境的步骤&#xff1a; 二、Accelerate Megatron-LM Plugin三、自定义训练过程四、检查点转换五、文本生成六、支持ROPE 、 ALiBi和Multi-Query Attention七、注意事项 一、Megatron-LM集成简介 在…

SystemVerilog学习(8)——包的使用

目录 一、包的定义 二、导出包的内容 1、可以通过域的索引符::号直接引用 2、可以指定索引一些需要的包中定义的类型到指定的容器中 3、通过通配符*来将包中所有的类别导入到指定容器中 三、包的使用 在进行本文的学习之前&#xff0c;首先需要对SV中类相关的内容有充分的认识…

mount /dev/mapper/centos-root on sysroot failed处理

今天发现centos7重启开不进去系统 通过查看日志主要告警如下 修复挂载目录 xfs_repair /dev/mapper/centos-root不行加-L参数 xfs_repair -L /dev/mapper/centos-root重启 reboot

2023年中国开式冷却塔应用现状及行业市场规模前景分析[图]

开式塔是目前应用最广、类型最多的一种冷却系统。循环水移走工艺介质或换热设备所散发的热量后成为热水&#xff0c;热水进入冷却塔后和空气直接接触&#xff0c;大部分热水得到冷却后&#xff0c;再循环使用。开式冷却塔又可以分为逆流式冷却塔和横流式冷却塔&#xff0c;按照…

基于Vue+SpringBoot的海南旅游景点推荐系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

【Linux】一

本文使用的是云服务器来获取Linux环境 (使用虚拟机同样可以学习使用命令), 并且介绍了常用的Linux 命令. 获取Linux环境 使用xshell连接到云服务器 1.新建会话 输入主机号(云服务器的外网ip) 2.输入用户名/密码 centos的用户名:root 密码就是在后台设置的 3.成功进入 ~描…

01ctfer 文件上传

01ctfer 文件上传 启动靶场 访问该地址 代码审计 <?php header("Content-Type:text/html; charsetutf-8"); // 每5分钟会清除一次目录下上传的文件 require_once(pclzip.lib.php);if(!$_FILES){echo <!DOCTYPE html> <html lang"zh">…

【Linux】Linux进程间通信(二)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;Linux &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【Linux】…

VUE指令、computed计算属性和watch 侦听器(附带详细案例)

文章目录 前言一、指令补充1. 指令修饰符2. v-bind对于样式操作的增强 - class3. 案例 - 京东秒杀 tab 导航高亮4. v-bind对于样式操作的增强 - style5. v-model应用于其他表单元素 二、computed计算属性1. 基础语法2. 计算属性 vS method 方法3. 完整写法4. 成绩案例 三、watc…