从C到C++过渡知识 下(深入理解引用与指针的关系)

news2024/11/24 3:20:56

引用

        引用时C++引入的一个新的概念,他和指针有着千丝万缕的关系。

        首先我们要了解的是引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。语法格式如下

int main()
{

	int a = 0;
	int& b =a;

	return 0;
}

        与指针十分的相似,&是b为引用的标志,int为引用对象的类型,所以int& b =a;表明b是变量a的引用。那这有什么用呢?下面我们看段代码。

        


int main()
{
	int a = 0;
	int& b =a;

	cout << a << " " << b << endl;
	b = 1;
	cout << a << " " << b << endl;

	return 0;
}

        这段代码的运行结果是什么呢?结果如下

        从结果不难看出,除了定义外,我们使用b就相当与使用a,修改b的值就相当于修改了a的值,他们两个使用同一块空间,我们可以通过下图的方式验证。

        

        通过打印地址我们不难发现,a与b的地址是相同的,所以我们使用b就相当于使用a。那么我们的C++之父为什么要加入引用的概念呢?上述操作用指针完全可以完成,为什么要多次一举呢?

        我们将上面的代码改写成如下指针操作,便可完成不使用a,却改变a的方法。

int main()
{
	int a = 0;
	int* b =&a;

	cout << a << " " << *b << endl;
	*b = 1;
	cout << a << " " << *b << endl;
	

	return 0;
}

        这两个方法达成的效果是完全一样的,但我们仔细观察他们的区别。

        指针方法下的b可以被修改指向,修改指向其他int类型变量。

        引用方法下b在创建的时候便以确定了唯一的指向,代表a,后序不可更改。相当于a的一个别名。一个人可以叫李华,可以取小名小李,小华但不论名字如何改变他都是指向李华这个人。

        这样看似乎没有太大的差别,但指针成也灵活败也灵活,指针b的地址可以写成任意的地址,编译器至少赋值时检查不出来。这就导致了一个严重的问题,如果你每次都正确的使用指针那没有什么问题,但如果你不小心让指针成为野指针,指向栈帧内的地址或已经free的内存,那么他就有可能改变我们原来的数据内容,造成严重的安全问题,这也是许多语言放弃指针的原因,尽管指针的效率十分高。

        而引用就完美的解决了这个问题,他右和指针差不多的效率,也可以修改对象的内容,而不会产生类似野指针造成的安全问题。他不可改变指向,你初始化为什么,他就只能修改指定向,不可以修改任意项。这也是祖师爷最初设计引用的初衷,增加代码的健壮性。

        在我们学习的初期,我们可以简单的理解引用为受限制的指针,例如如下代码。const修饰b,b从语法上限制不可修改。而引用就是编译器帮我i们完成了解引用的过程,并且将int&重新定义为一种限制的语法。

int main()
{
	int a = 0;
	int* const b =&a;

	cout << a << " " << *b << endl;
	*b = 1;
	cout << a << " " << *b << endl;
	
	return 0;
}

        如果我们试图修改b的指向便会报出如下的错误。

        其实编译器对于指针和引用实在看待同一种东西,引用就是指针的再次封装。为什么可以这么说呢?我们看汇编代码,当然也只是一些简单的汇编指令。

        lea是Load Effective Adreess 加载有效地址,mov 是把后面的值赋值给前面。我们可以发现编译器把a的地址给了b和p,但因为p与b的类型不同,他们在使用的时候,编译器的处理不同。如下图。

        我们可以发现b在调用的时候是先将b里存的地址加载到寄存器中,然后根据这个地址进行赋值操作,相当于编译器层面帮我们做了次解引用。

        而指针p是直接对p的地址进行更改,没有编译器的解引用。

        读者再仔细看上面的程序,会发现指针仅仅使用一次强制类型转换,他存的地址便可以是任意地址,然后我们根据这些地址进行修改,便会产生难以预料的后果。这也体现了指针的不安全性。而引用有和指针几乎相同的效率,但安全性却大大的提高了!!

        下面我们用引用和指针写一个简单的交换两数的程序。

void Swap(int* p1, int* p2)
{
	int t = *p1;
	*p1 = *p2;
	*p2 = t;
}
void Swap(int& p1, int& p2)
{
	int t = p1;
	p1 = p2;
	p2 = t;
}

        以上两个代码都可以完成交换两数的程序,且用引用在调用函数的时候不用取地址,相对而言更简单些。

内联函数

        例如上面我们写的Swap函数,在排序的时候,函数调用十分的频繁,而函数每次调用都会加建立栈帧,销毁栈帧消耗一定的时间。有没有一种方法可以在处理这些频繁的小问题上不用调用函数这么大费周章?

        相信熟悉C语言的读者一定想到了宏函数的使用,但宏函数是十分容易出错的。我们看一个简单的宏函数,大家先自己仔细阅读,看看有问题么?

#define ADD (a,b) a+b;

        这看起来十分的正常,没有什么问题但我们仔细观察便会发现问题不小。

        首先第一个错误ADD与后面的括号有空格,不要小看这一细节。有空格就代表是单纯的宏替换,而不是宏函数不可以传参。进行下面的展开。

        删除空格才能是宏函数,有如下传参展开。

        那么分号要加么?结果是否定的。我们可能写出如下的代码。那么他就达不到我们想要的结果却可以正常运行。宏展开后为 c=a+b; +4;在加4前的分号导致c没有加4就结束了。

int main()
{
	int a = 0;
	int b = 1;
	int c = 4;

	c=ADD(a,b)+4;

	return 0;
}

        其次要考虑的便是展开后的优先级问题。如下代码

c=4*ADD(a,b)+4;

        我们展开后的代码如下c=4*a+b+4;我们想要a+b的结果乘以4,但由于优先级的问题变成了4*a+b,显然与我们的意图不一样,解决方法也十分简单,在外围加个括号便可以了,保证内部优先级最高,但这就完成了么?还是没有。

        假如我们有如下的代码,

c=4*ADD(a|4,b);

        展开后的结果为c=4*(a|4+b);但+的优先级比|高,就会导致运行先4+b然后与a按位或。我们因此要对a,b也加上括号,最终的结果如下。


#define ADD(a,b) ((a)+(b))

        一个小小的ADD都有如此多的细节,其他的也不逞多让,不简单。但如果我们写出函数的话,就不用考虑那么多了。没有优先级的问题与其他细节。但消耗的时间大于宏函数。

int ADD(int a, int b)
{
	return a + b;
}

        此时便可以使用内联函数,向编译器申请将代码展开。内联函数的定义十分简单,只需要加上关键字inline即可。

inline int ADD(int a, int b)
{
	return a + b;
}

        编译器会根据函数的大小决定是否在函数调用语句将代码展开。从而减少函数栈帧的消耗。

        今天的文章就到此结束了,喜欢的点点关注。如果有错误,欢迎在评论区指出。

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

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

相关文章

hibernate session接口

hibernate session接口 Session接口是hibernate向应用程序提供的操纵数据库的最主要的接口&#xff0c;提供了保存、更新、删除和加载Java对象的方法。 session具有一个缓存&#xff0c;位于缓存中的对象成为持久化对象&#xff0c;和数据库中的相关记录对应。session能够在某些…

智能视频翻译和配音处理工具:Pyvideotrans

pyVideoTrans&#xff1a;一键字幕识别翻译配音带新语言字幕和配音的视频 - 精选真开源&#xff0c;释放新价值。 概览 Pyvideotrans是一款卓著的智能化视频处理系统&#xff0c;专精于视频翻译与配音艺术&#xff0c;以其卓越的技术实力实现对原始视频中音频信息的精准捕捉、…

CCIE-03-Layer2-LAN-TS

目录 实验条件网络拓朴实验目标 开始排错问题1. SW2上的DHCP中继没有配置正确问题2. SW1/SW2的SVI接口被关闭问题3. 安全端口配置了不同的MAC地址 实验条件 网络拓朴 Output1 Output2 实验目标 排除故障使得PC101访问Server1时符合图片中给出的Output 开始排错 根据要求…

并发编程01-深入理解Java并发/线程等待/通知机制

为什么我们要学习并发编程&#xff1f; 最直白的原因&#xff0c;因为面试需要&#xff0c;我们来看看美团和阿里对 Java 岗位的 JD&#xff1a; 从上面两大互联网公司的招聘需求可以看到&#xff0c; 大厂的 Java 岗的并发编程能力属于标配。 而在非大厂的公司&#xff0c; 并…

基于栈结构的非递归二叉树结点关键字输出算法

基于栈结构的非递归二叉树结点关键字输出算法 一、引言二、二叉树基本概念三、非递归遍历算法基础四、算法设计五、算法实现六、C代码示例七、算法分析八、优化与讨论 一、引言 在计算机科学中&#xff0c;二叉树是一种重要的数据结构&#xff0c;它广泛应用于各种算法和数据结…

笔记: javaSE day17天笔记

第十七天课堂笔记 Java常用类 数学类★★★ math java.lang.Math , 数学类 round(x) : 四舍五入 , 把 x加0.5 后向下取整 ceil(x) : 返回大于等于x的最小整数 , 向上取整 floor(x) : 返回小于等于x的最大整数 , 向下取整 sqrt(x) : 平方根 cbrt(x): 立方根 pow(a , b)…

App应用的服务器如何增加高并发能力

大家好&#xff01;我是你们的好朋友咕噜铁蛋&#xff01;近年来&#xff0c;随着移动互联网的蓬勃发展&#xff0c;各类App应用如雨后春笋般涌现&#xff0c;用户量呈现爆发式增长。然而&#xff0c;随之而来的高并发访问问题也开始频繁出现&#xff0c;给服务器带来了极大的挑…

ecology9.0通过自定义按钮给明细表某字段赋值

功能&#xff1a;把主表字段赋值给明细表字段 核心代码&#xff1a; <script>jQuery(document).ready(function(){$(#setcgy).click(function(){var cgy_txt WfForm.getBrowserShowName("field1207");debuggervar cgy WfForm.getFieldValue("field1207…

java(5)之数组

1、什么是数组 数组就是用来储存相同数据类型的数据集合&#xff0c;可使用共同的名称来引用数组中的数据&#xff0c;数组的类型有很多种&#xff0c;但是指定了数组的类型就只能存储这一类数据。 2、数组的特点 1、既能存储原始数据也能催出对象类型 2、长度一旦确定就不…

MySQL进阶-----SQL提示与覆盖索引

目录 前言 一、SQL提示 1.数据准备 2. SQL的自我选择 3.SQL提示 二、覆盖索引 前言 MySQL进阶篇的索引部分基本上要结束了&#xff0c;这里就剩下SQL提示、覆盖索引、前缀索引以及单例联合索引的内容。那本期的话我们就先讲解SQL提示和覆盖索引先&#xff0c;剩下的内容就…

ssm 设备采购管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm 设备采购管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

使用 Docker Compose 部署邮件服务器

使用 Docker Compose 部署邮件服务器 很多时候为了方便&#xff0c; 我们都直接使用第三方邮箱进行收发邮件。 但第三方邮箱有些要求定期修改密码&#xff0c;有些限制发邮箱的次数&#xff0c; 对于一些个人和企业来说&#xff0c; 有自己的域名和服务器为什么不自己搭建一个邮…

PEFT-LISA

LISA是LoRA的简化版&#xff0c;但其抓住了LoRA微调的核心&#xff0c;即LoRA侧重更新LLM的底层embedding和顶层head。 根据上述现象&#xff0c;LISA提出两点改进&#xff1a; 始终更新LLM的底层embedding和顶层head随机更新中间层的hidden state 实验结果 显存占用 毕竟模型…

ZKP价值链路的垂直整合

1. ZKP proof生命周期 从ZKP&#xff08;zero-knowledge proof&#xff09;生命周期&#xff0c;先看围绕ZKP的价值链路形成&#xff1a; 1&#xff09;User intent用户意图&#xff1a;以某用户意图为起点&#xff0c;如想要在某zk-rollup上swap某token、证明其身份、执行某…

HarmonyOS 和 OpenHarmony

HarmonyOS 和 OpenHarmony 支持的 shell 命令不同&#xff0c;因此有时候需要做一做区分&#xff0c;目前有些文档上没有标注&#xff0c;因此可能产生歧义。 HarmonyOS 支持 getprop&#xff1a; getprop hw_sc.build.os.apiversion # 查看API版本OpenHarmony 上支持 param…

华为ensp中ospf多区域管理 原理及配置命令(详解)

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; ————前言———— OSPF 多区域的主要作用是缩小链路状态数据库和路由表的规模&#xff0c;减少路由更新的频率&#xff0c;提高网络的可扩展性&#xff0c;实现路由过滤和路由汇总&#xff0…

Java多线程实战-从零手搓一个简易线程池(三)线程工厂,核心线程与非核心线程逻辑实现

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️本系列源码仓库&#xff1a;多线程并发编程学习的多个代码片段(github) &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正…

使用ARCore深度API实现点云采集

一、深度API 本小节内容摘自ARCore官方文档。 ARCore 深度API Depth API 可助力实现对象遮挡、提升沉浸感和新颖的互动体验&#xff0c;从而增强 AR 体验的真实感。 在下图中&#xff0c;右侧画面是采用深度API进行遮挡后的效果&#xff0c;与左侧图相比更加真实。 深度值 给…

【热门话题】WebKit架构简介

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 WebKit架构简介一、引言二、WebKit概览1. 起源与发展2. 模块化设计 三、WebCore…

补充知识

补充知识1 内存的本质是对数据的临时存储 内存与磁盘进行交互时&#xff0c; 最小单位是4kb叫做页框(内存)和页帧(磁盘) 也就是&#xff0c; 如果我们要将磁盘的内容加载到内存中&#xff0c; 可是文件大小只有1kb&#xff0c; 我们也要拿出4kb来存他&#xff0c; 多余的就直…