【C++】初阶 --- 引用(超级详细版!!!)

news2024/11/20 10:39:22

文章目录

  • 🍪一、引用的概念
  • 🍪二、引用的特性
    • 🍿1、引用在定义时必须初始化
    • 🍿2、一个变量可以有多个引用
    • 🍿3、引用一旦引用一个实体,再不能引用其他实体
  • 🍪三、常引用(被const 修饰的引用)
    • 🍿1、权限的放大
    • 🍿2、权限的平移
    • 🍿3、权限的缩小
    • 🍿4、临时变量具有常性
  • 🍪四、引用的使用场景
    • 🍿1、做参数(传引用传参)
    • 🍿2、做返回值(引用做返回值)
    • 🍿3、传值、传引用效率比较
      • 🥗 传值和传引用的作为参数的性能比较
      • 🥗 传值和传引用的作为返回值的性能比较
    • 🍿4、传引用返回修改返回对象
    • 🍿5、总结
  • 🍪五、引用和指针的区别

在这里插入图片描述

🍪一、引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"
比如:抓 “周树人” 和我 "鲁迅"有什么关系🤭,本质上其实是一个人
在这里插入图片描述

🚩引用的操作符: &
🚩类型& 引用变量名(对象名) = 引用实体

🌰举个栗子演示👇

int main()
{
	int a = 0;
	int& b = a; //引用

	cout << &a << endl;
	cout << &b << endl;

	return 0;
}

在这里插入图片描述

☝️代码段中相当于给变量a取了一个别名b,通过标识名b可以在其被定义的作用域中访问变量a,可以看到地址是一样的,说明a和b共用同一块内存空间

🚨注意:引用类型必须和引用实体是同种类型的

🍪二、引用的特性

🍿1、引用在定义时必须初始化

🥰请看代码与注释👇

void TestRef()
{
	int a = 10;
	// int& ra;  // 该条语句编译时会出错
	int& ra = a;
	
	cout << a << "  " << &a << endl;
	cout << ra << "  " << &ra << endl;
}

int main()
{
	TestRef();

	return 0;
}

🚨int& ra; 该条语句编译时会出错,是不可以的,必须要进行初始化

在这里插入图片描述

🍿2、一个变量可以有多个引用

🚩一个变量可以有多个引用,并且引用可以嵌套定义

🥰请看代码与注释👇

void TestRef()
{
	int a = 10;
	// int& ra;  // 该条语句编译时会出错
	int& ra = a; // ra是a的引用
	int& rra = a; // rra是a的引用
	int& rrra = ra; // rrra是ra的引用
	
	cout << a << "  " << &a << endl;
	cout << ra << "  " << &ra << endl; 
	cout << rra << "  " << &rra << endl;
	cout << rrra << "  " << &rrra << endl;
}

int main()
{
	TestRef();

	return 0;
}

在这里插入图片描述

基于引用这种可以嵌套定义并且无需多次解引用就可以直接访问被引用变量的这种特性,很多时候使用引用可以避免多级指针的出现

🍿3、引用一旦引用一个实体,再不能引用其他实体

🌰举个栗子演示👇

void TestRef()
{
	int a = 10;
	int b = 20;

	int& x = a; 
	int& x = b; 
	
	cout << a << "  " << &a << endl;
	cout << x << "  " << &x << endl; 
	cout << b << "  " << &b << endl;
	cout << x << "  " << &x << endl;
}

在这里插入图片描述

由于这个特性,引用无法完全代替指针(比如链表中结构体的next指针无法用引用来代替,因为引用一旦引用一个实体,再不能引用其他实体),灵活性也不如指针,但是引用也因此比指针更安全,这也是引用这个语法的设计初衷之一(使用指针很容易出现野指针,非法访问内存空间的情况)

🍪三、常引用(被const 修饰的引用)

🚨在引用的过程中,权限可以平移,权限可以缩小,但是权限不能放大

🍿1、权限的放大

假如a是鲁智深 可以喝酒 可以吃肉 不能杀人
给a取个别名b 叫花和尚 可以喝酒 可以吃肉 可以杀人?
a(鲁智深)和 b(花和尚)是同一个人,当然不可以杀人

🌰举个栗子👇

int main()
{
	//权限的放大
	const int a = 0;
	int& b = a;

	return 0;
}

🍿2、权限的平移

🌰举个栗子👇

int main()
{
	//权限的平移
	const int a = 0;
	const int& b = a;

	return 0;
}

🍿3、权限的缩小

🌰举个栗子👇

int main()
{
	//权限的缩小
	int a = 0; //a可以修改,可以影响b
	const int& b = a;

	return 0;
}

a可以修改,可以影响b

🍿4、临时变量具有常性

🌰举个栗子👇

int main()
{
	int i = 0;
	double& d = i;//临时变量具有常性

	return 0;
}

在这里插入图片描述
✅正确操作👇

int main()
{
	int i = 0;
	const double& d = i;

	return 0;
}

🚩这里涉及一个知识点:
代码段中b去引用i,i会发生隐式类型转换,i转换的结果会存入一个临时空间中。
(当赋值等号右边有运算表达式或有变量发生类型转换时,表达式或类型转换的结果都会先存入一个临时空间后再赋值给等号左边的变量)

因此这里的d引用的实质上是一块临时空间:
在这里插入图片描述

🍪四、引用的使用场景

🍿1、做参数(传引用传参)

🌰举个栗子👇

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a = 10;
	int b = 20;

	Swap(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	return 0;
}

在这里插入图片描述

🍿2、做返回值(引用做返回值)

先看一下传值返回👇

//传值返回    
int Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}

int main()
{
	int ret = Count();
	cout << ret << endl;

	return 0;
}

我们非常的熟悉,结果为 1

再来看一下下面这段代码👇

//传引用返回
int& Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}

int main()
{
	int ret = Count();
	//这里打印的结果可能是1,也可能是随机值
	cout << ret << endl;

	return 0;
}

在这里插入图片描述

🚨这里输出的结果有两种可能,一种为 1 ,另一种可能为随机值,原因是:传引用返回返回的是n的别名,但是这里存在的问题是我们返回n的别名也就是访问n这块空间,访问n这块空间就有两个结果:如果这个栈帧没有清除,它的值就是1,如果它的空间被清了,那么它的就会是一个 随机值

看图比较一下两段代码👇
在这里插入图片描述

上面的理解了之后,再看一段代码👇

int& Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}

int main()
{
	int& ret = Count();
	//这里打印的结果可能是1,也可能是随机值
	cout << ret << endl;
	cout << ret << endl; //被覆盖

	return 0;
}

在这里插入图片描述
这是为什么呢?

🚨函数调用要先传参,也就是先取值,这个时候还没有建立栈帧,取值之前还没有被覆盖,传参过去之后,建立栈帧,值不会受到影响;那第二次调用,再去取值,这时这个值已经被建立的栈帧覆盖了,所以输出的是随机值

理解了之后,再看一段代码👇

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

在这里插入图片描述
这样的函数不能使用引用返回,是非常不安全的,相当于野引用(野指针)的方式
在这里插入图片描述

🚨注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回

🍿3、传值、传引用效率比较

🥗 传值和传引用的作为参数的性能比较

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();

	return 0;
}

在这里插入图片描述

🥗 传值和传引用的作为返回值的性能比较


#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();
	
	return 0;
}

在这里插入图片描述

🍿4、传引用返回修改返回对象

传引用返回还有一个隐藏作用:

假如一个顺序表要进行读和修改数据

struct SeqList
{
	int* a;
	int size;
	int capacity;
};

🚩C的接口设计:

//C的接口设计

//读取第i个位置的值
int SLAT(struct SeqList* ps, int i)
{
	assert(i < ps->size);
	//...
	return ps->a[i];
}
//修改第i个位置的值
void SLModify(struct SeqList* ps, int i, int x)
{
	assert(i < ps->size);

	//...
	ps->a[i] = x;
}

🚩CPP的接口设计:

//CPP的接口设计

//读 or 修改第i个位置的值
int& SLAT(struct SeqList& ps, int i)
{
	assert(i < ps.size);
	//...
	return ps.a[i];
}

int main()
{
	struct SeqList s;
	//...
	SLAT(s, 0) = 10;//修改
	SLAT(s, 1) = 20;
	SLAT(s, 2) = 30;
	cout << SLAT(s, 0) << endl;//打印
	cout << SLAT(s, 1) << endl;
	cout << SLAT(s, 2) << endl;

	return 0;
}

这里相比 C的接口设计来看,非常的香,减少了拷贝🥰

🍿5、总结

🚩传引用传参(任何时候都可以)

1、提高效率
2、输出型参数(形参的修改,影响实参)

🚩传引用返回(出了函数作用域对象还在才可以用)

1、提高效率
2、修改返回对象

🍪五、引用和指针的区别

🚩在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
🚩在底层实现上实际是有空间的,因为引用是按照指针方式来实现的

int main()
{
	int a = 0;
	int* p1 = &a;
	int& ref = a;
	
	return 0;
}

我们来看下引用和指针的汇编代码对比:
在这里插入图片描述
🥰可以看到:引用和指针底层是一样的,可以说是引用就是化了妆的指针

引用和指针的不同点:

🚩1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
🚩2. 引用在定义时必须初始化,指针没有要求
🚩3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
🚩4. 没有NULL引用,但有NULL指针
🚩5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
🚩6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
🚩7. 有多级指针,但是没有多级引用
🚩8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
🚩9. 引用比指针使用起来相对更安全

😍这期内容有一点难理解,希望烙铁们能理解消化,有所收获哦!

总结🥰
以上就是 【C++】引用 的全部内容啦🥳🥳🥳🥳
本文章所在【C++初阶】专栏,感兴趣的烙铁可以订阅本专栏哦🥳🥳🥳
前途很远,也很暗,但是不要怕,不怕的人面前才有路。💕💕💕
小的会继续学习,继续努力带来更好的作品😊😊😊
创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰

请添加图片描述

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

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

相关文章

idea打开传统eclipse项目

打开传统web项目 1.打开后选择项目文件 2.选择项目结构 3.设置jdk版本 4.导入当前项目模块 5.选择eclipse 6. 设置保存目录 7.右键模块&#xff0c;添加spring和web文件 8. 设置web目录之类的&#xff0c;并且创建打包工具 9.如果有本地lib&#xff0c;添加为库 最后点击应用&…

【linux】Linux桌面应用程序快捷方式

在linux系统里&#xff0c;很多应用程序虽然有对应的版本&#xff0c;但是下载了之后发现打开方式并不友好&#xff0c;比如&#xff0c;今天下载了DataGrip&#xff0c;打开文件夹才发现它里面有这些&#xff1a; 红框内的脚本是其正确的打开方式。每次你都要执行&#xff1a…

一篇文了解SHA2代码签名

在当今数字时代&#xff0c;各种网络隐私安全威胁层出不穷&#xff0c;对此&#xff0c;我们也采取了很多安全措施。SHA2代码签名作为一种非常重要的安全措施&#xff0c;它有助于确保软件代码和文件的完整性和真实性。那么你知道SHA2代码签名是什么&#xff1f;它的原理是什么…

天线辐射机制

电磁场如何从源中产生并最终脱离天线辐射到自由空间中去的呢&#xff1f;让我们首先来研究一下一些基本的辐射源。 1、单线Single Wire 导线是一种电荷运动产生电流特性的材料&#xff0c;假设用qv&#xff08;库仑/m3&#xff09;表示的一个电体积电荷密度均匀分布在一个横截…

云安全攻防(五)之 容器基础设施所面临的风险

容器基础设施所面临的风险 容器基础设施面临的风险 我们从容器镜像、活动容器、容器网络、容器管理程序接口、宿主机操作和软件漏洞六个方面来分析容器基础设施可能面临的风险 容器镜像存在的风险 所有容器都来自容器镜像。与虚拟机镜像不同的是&#xff0c;容器镜像是一个不…

基于埋点日志数据的网络流量统计 - PV、UV

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 一、 网站总流量数统计 - PV 1. 需求分析 2. 代码实现 方式一 方式二 方式三&#xff1a;使用process算子实现 方式四&#xff1a;使用process算子实现 二、网站独立访客数统计 - UV 1. …

新闻稿代写软件有哪些?聪明灵犀工具助你撰写合格新闻稿

新闻稿代写软件有哪些&#xff1f;新闻稿是一种重要的宣传工具&#xff0c;但是撰写优秀的新闻稿需要一定的写作技巧和经验。幸运的是&#xff0c;现在有许多新闻稿代写软件可供使用&#xff0c;这些工具可以帮助你撰写出更优质的新闻稿。本文将介绍一些常用的新闻稿代写软件以…

【性能调优-实例演示】CPU爆了怎么定位问题--》调试指令性能分析工具

性能调优 定位生产性能问题 生产环境&#xff0c;CPU Memory 告警 top&#xff1a;找出占CPU比较高的进程${pid}&#xff08;内存增长&#xff0c;CPU居高不下&#xff09;top -Hp ${pid}&#xff1a;显示所有线程的CPU占比&#xff0c;观察进程中的线程&#xff0c;找出哪个…

Vol的学习

首先学习基础用法 1.查看系统基本信息 vol.py -f 路径 imageinfo 2.查看进程命令行 vol.py -f 路径 --profile系统版本 cmdline vol.py -f 路径 --profile版本 cmdscan 3.查看进程信息 vol.py -f 路径 --profile系统 pslist 通过树的方式返回 vol.py -f 路径 --profile系统…

淘宝资源采集(从零开始学习淘宝数据爬取)

1. 为什么要进行淘宝数据爬取&#xff1f; 淘宝数据爬取是指通过自动化程序从淘宝网站上获取数据的过程。这些数据可以包括商品信息、销售数据、评论等等。淘宝数据爬取可以帮助您了解市场趋势、优化您的产品选择以及提高销售额。 淘宝作为全球的电商平台&#xff0c;每天都有…

从初学者到专家:Java 数据类型和变量的完整指南

目录 一、字面常量 1.1什么常量&#xff1f; 1.2常见的六种常量类型 二、数据类型 2.1什么是数据类型&#xff1f; 2.2基本数据类型&#xff1a; 2.3引用数据类型 三、变量 3.1什么是变量&#xff1f; 3.2变量的命名规则 3.3变量的作用域 3.4变量的被final修饰 四…

C++ 外部变量和外部函数

1.外部变量 如果一个变量除了在定义它的源文件中可以使用外&#xff0c;还能被其他文件使用&#xff0c;那么就称这个变量为外部变量。命名空间作用域中定义的变量&#xff0c;默认情况下都是外部变量&#xff0c;但在其他文件中如果需要使用这一变量&#xff0c;需要用extern…

CAS - 原理简介

CAS是JDK提供的非阻塞原子操作&#xff0c;它通过硬件保证了比较-更新的原子性。它是非阻塞的且自身具有原子性&#xff0c;也就是说CAS效率高、可靠。CAS是一条CPU的原子指令(cmpxchg指令)&#xff0c;不会造成所谓的数据不一致问题&#xff0c;Unsafe类提供的CAS方法&#xf…

【内网穿透】内网穿透应用场景

伴随着科学技术的进步&#xff0c;我们身边出现了越来越多的电子设备&#xff0c;特别是移动电子设备的普及&#xff0c;给我们的生活带来极大的便利&#xff0c;而软件技术的发展&#xff0c;更为这些软件设备带来更多应用的可能。虽然移动设备覆盖了了我们生活的绝大部分场景…

Mybatis引出的一系列问题-JDBC 的探究

1 引入对JDBC的理解-1 一般来说&#xff0c;Java应用程序访问数据库的过程是&#xff1a; 装载数据库驱动程序&#xff1b;通过jdbc建立数据库连接&#xff1b;访问数据库&#xff0c;执行sql语句&#xff1b;断开数据库连接。 Public void FindAllUsers(){//1、装载sqlserve…

面试热题(接雨水问题)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 我们看到题的第一步&#xff0c;永远是对入参进行判断 public int trap(int[] height) {if (height null) {return 0;}...} 但是我们想想看&#xff0c;接…

uniapp微信小程序底部弹窗自定义组件

基础弹窗效果组件 <template><view><viewclass"tui-actionsheet-class tui-actionsheet":class"[show ? tui-actionsheet-show : ]"><view class"regional-selection">底部弹窗</view></view><!-- 遮罩…

教你使用Pyinstaller将Python源码打包成可执行程序exe的方法

pyinstaller是一个常用的Python打包工具&#xff0c;可以将Python程序打包成独立的可执行文件&#xff0c;支持Windows、Linux和macOS等平台。 ★★★Pyinstaller有许多参数&#xff0c;以下是其中一些主要参数的含义&#xff1a; -F, --onefile&#xff1a;打包一个单个文件…

Excel如何把两列互换

第一步&#xff1a;选择一列 打开excel&#xff0c;选中一列后将鼠标放在列后&#xff0c;让箭头变成十字方向。 第二步&#xff1a;选择Shift键 按住键盘上的Shift键&#xff0c;将列往后移动变成图示样。 第三步&#xff1a;选择互换 完成上述操作后&#xff0c;松开鼠标两…

Netty框架:ByteBuf空间不够时自动扩充空间

说明 使用Netty的ByteBuf&#xff0c;空间不够时可以自动扩充。扩充时&#xff0c;不是一个字节一个字节的扩充&#xff0c;而是扩充一段空间。对于不同的ByteBufAllocator实现&#xff0c;每次扩充的空间大小也不相同。 代码举例 使用Unpooled分配ByteBuf 下面这段代码&am…