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

news2024/11/28 10:44:57

目录

数组名的理解

使用指针访问数组

一维数组传参的本质

冒泡排序

二级指针

指针数组

指针数组模拟二维数组

字符指针变量

数组指针变量

二维数组传参的本质

函数指针变量

函数指针变量的创建 

函数指针变量的使用

两段有趣的代码

代码一

代码二

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语句代表一种算法(加、减、乘、除) ,但是我们发现相似的代码出现了多份,显得有些冗余,这应该怎么办呢?

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Saas+AI?这可能是2023年最精华的6篇文章

‍ 原文太长&#xff08;6篇总计40200字&#xff09;&#xff0c;我提炼出核心要点&#xff0c;并打散重组&#xff0c;最后总计仅4500字&#xff0c;不仅是节省了大家时间&#xff0c;还能带来更多不一样的视角解读。 文章一、「AI与SaaS结合的三部曲」 &#xff08;引自8月25…

计及源荷不确定性的综合能源生产单元运行调度与容量配置随机优化模型MATLAB

主要内容 本程序复现《计及源荷不确定性的综合能源生产单元运行调度与容量配置两阶段随机优化》模型&#xff0c;采用全年光伏、风电数据通过kmeans聚类得到6种场景&#xff0c;构建了随机优化模型&#xff0c;在研究融合P2G与CCS的IEPU系统框架基础上&#xff0c;建立了各关键…

JWT登录认证(3拦截器)

Jwt登录认证&#xff08;拦截器&#xff09;&#xff1a; 使用拦截器统一验证令牌 登录和注册接口需要放行 interceptors.LoginInterceptor&#xff1a;&#xff08;注册一个拦截器&#xff09; package com.lin.springboot01.interceptors;import com.lin.springboot01.pojo.…

设计模式-中介者模式-笔记

Medicator中介者模式 动机&#xff08;Motivation&#xff09; 在软件构建过程中&#xff0c;经常会出现多个对象相互关联交际的情况&#xff0c;对象之间常常会维持一种复杂的引用关系&#xff0c;如果遇到一些需求的更改&#xff0c;这种直接的引用关系将面临不断的变化。 …

电脑监控软,电脑屏幕监控软件

电脑监控软&#xff0c;电脑屏幕监控软件 电脑屏幕监控软件不仅仅是一种工具&#xff0c;更是一种守护。随着互联网的发展&#xff0c;我们工作越来越离不开电脑&#xff0c;但同时&#xff0c;也面临着更多的安全隐患。为了保护个人隐私&#xff0c;提高工作效率&#xff0c;…

Java基础笔记

1.数据类型在java语言中包括两种: 第一种:基本数据类型 基本数据类型又可以划分为4大类8小种: 第一类:整数型 byte , short,int, long(没有小数的&#xff09; 第二类:浮点型 float,aouble(带有小数的&#xff09; 第三类:布尔型 boole…

wpf devexpress在未束缚模式中生成Tree

TreeListControl 可以在未束缚模式中没有数据源时操作&#xff0c;这个教程示范如何在没有数据源时创建tree 在XAML生成tree 创建ProjectObject类实现数据对象显示在TreeListControl: public class ProjectObject {public string Name { get; set; }public string Executor {…

UE基础篇十:材质

导语: 视频文档在文末 虚幻引擎默认是延迟渲染(延迟渲染是通过先算出需要着色的像素,然后再迭代灯光,从而减少大量无效的灯光计算,来达到优化的目的) 一、基础知识 1.1 贴图分辨率尺寸 2的幂次方,长宽随意组合 非2的幂次方,不能设置MipMaps(引擎会生成多张分辨率更…

揭露 bbr 的真相

信 bbr 的伙计们&#xff0c;我又要泼冷水了&#xff0c;哈哈。 从先 bbr 的海报开始&#xff0c;相信大家也是被它唬住的&#xff1a; 注意横坐标标度是对数&#xff0c;这就凸显了优势。 把它展开到自然数坐标&#xff0c;再把其它对照画在一个坐标系里&#xff0c;在此之…

qt使用AES加密、解密字符串

一、AES算法 AES (Advanced Encryption Standard) 是一种对称加密算法&#xff0c;是目前被广泛使用的数据加密标准之一。该算法旨在取代DES (Data Encryption Standard) 算法。AES最初由比利时密码学家 Joan Daemen 和 Vincent Rijmen 提出&#xff0c;经过多年的演化、改进和…

Sqlite安装配置及使用

一、下载SQLite Sqlite官网 我下载的是3370000版本:sqlite-dll-win64-x64-3370000.zip 和 sqlite-tools-win32-x86-3370000.zip 二、解压下载的两个压缩包 三、配置环境 四、检查是否安装配置成功 winR&#xff1a;输入cmd调出命令窗口&#xff0c;输入sqlite3后回车查看s…

2023年咸阳市《网络建设与运维》赛题解析------四、安全配置

安全配置 说明:IP地址按照题目给定的顺序用“ip/mask”表示,IPv4 any地址用0.0.0.0/0,IPv6 any地址用::/0,禁止用地址条目,否则按零分处理。 1.FW1配置IPv4 nat,实现集团产品1段IPv4访问Internet IPv4,转换ip/mask为200.200.200.16/28,保证每一个源IP产生的所有会话将…

Python 如何实现外观设计模式?什么是 Facade 外观设计模式?Python 设计模式示例代码

什么是&#xff08;Facade&#xff09;外观设计模式&#xff1f; 外观&#xff08;Facade&#xff09;设计模式是一种结构型设计模式&#xff0c;它提供了一个简化复杂系统接口的高级接口&#xff0c;使得系统更容易使用。外观模式通过定义一个高层接口&#xff0c;隐藏了系统…

花 200 元测试 1300 个实时数据同步任务

背景 对于将数据作为重要生产资料的公司来说&#xff0c;超大规模的数据迁移同步系统( 1k、5k、10k 条同步任务)是刚需。 本文以此为出发点&#xff0c;介绍近期 CloudCanal 所做的一个容量测试&#xff1a;在单个 CloudCanal 集群上创建 1300 实时任务&#xff0c;验证系统是…

2023年中国逆流式冷却塔性能特点、应用领域及市场规模分析[图]

按冷却塔热交换时气流和水流方向不同的配置&#xff0c;机力通风冷却塔又可分为横流式冷却塔、逆流式冷却塔&#xff0c;目前主流的冷却塔型式为逆流式冷却塔&#xff0c;逆流式冷却塔&#xff08;counterflowcoolingtower&#xff09;是指水流在塔内垂直落下&#xff0c;气流方…

思维导图软件 Xmind mac中文版特点介绍

XMind 2022 mac是一款思维导图软件&#xff0c;可以帮助用户创建各种类型的思维导图和概念图。 XMind mac软件特点 - 多样化的导图类型&#xff1a;XMind提供了多种类型的导图&#xff0c;如鱼骨图、树形图、机构图等&#xff0c;可以满足不同用户的需求。 - 强大的功能和工具&…

扩散模型实战(十):Stable Diffusion文本条件生成图像大模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 扩散模型实战&#xff08;四&#xff…

(免费领源码)基于Vue+Node.js的宠物领养网站的设计与开发83352-计算机毕业设计项目选题推荐

摘 要 随着互联网大趋势的到来&#xff0c;社会的方方面面&#xff0c;各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去&#xff0c;而其中最好的方式就是建立网络管理系统&#xff0c;并对其进行信息管理。由于现在网络的发达&#xff0c;宠物领养网站的…

User parameters自定义用户参数 (zabbix监控)

1、介绍和用法 ① 介绍 自定义用户参数&#xff0c;也就是自定义key 有时&#xff0c;你可能想要运行一个代理检查&#xff0c;而不是Zabbix的预定义 你可以编写一个命令来检索需要的数据&#xff0c;并将其包含在代理配置文件("UserParameter"配置参数)的用户参数中…

m1 rvm install 3.0.0 Error running ‘__rvm_make -j8‘

在使用M1 在安装cocopods 前时&#xff0c;安装 rvm install 3.0.0遇到 rvm install 3.0.0 Error running __rvm_make -j8 备注: 该图片是借用其他博客图片&#xff0c;因为我的环境解决完没有保留之前错误信息。 解决方法如下&#xff1a; 1. brew uninstall --ignore-depe…