【是C++,不是C艹】 引用的概念 | 引用的使用 | 引用与指针的区别

news2024/11/18 23:46:17

💞💞欢迎来到 Claffic 的博客💞💞

  👉 专栏:《是C++,不是C艹》👈

前言:

前面带大家学习了函数重载等C++基础,这期继续C++基础的学习:引用。

注:

你最好是学完了C语言,并学过一些初阶的数据结构。


(没有目录) ヽ( ̄ω ̄( ̄ω ̄〃)ゝ 

Part1:何为引用

1.一个引子

不知道大家听没听过这个梗:

❓“抓捕周树人跟我鲁迅有什么关系”❓

这是《楼外楼》当中的一个桥段:

一个军官拿着逮捕令前去抓捕鲁迅的时候,鲁迅淡定地看了眼逮捕令,随即表示军官要抓的是周树人,和自己鲁迅没有关系。在鲁迅说完之后,军官居然真的相信并离开了。 

鲁迅先生巧妙地用别名自保,实在是高哇 🤣🤣🤣 (那军官也是没文化)。

抓捕周树人跟我鲁迅有什么关系? 在我们看来,当然有关系啦,鲁迅是周树人的笔名之一啊。

其实周树人还有很多很多笔名,大家可以自行查阅... ...

不管先生有多少笔名,都是指的一个人:周树人。

☝️这就有引用的意思,接下来进入正题:

2.概念

你单看引用,想到的是语文课上的一种手法:此处运用了引用的手法,引用了... ... 的话,... ...

这种理解方式是有大偏差的,应该理解为 “起别名” : 

引用 不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

重点是 会为别名开辟内存空间

引用的使用:

类型& 引用变量名(对象名) = 引用实体;

        ☝️你以为是取地址? 错,这是放在数据类型后面的 & ,🐟取地址区分开!

void TestRef()
{
	int a = 10;
	int& ra = a; // 定义引用类型

	printf("%p\n", &a); // 输出a的地址
	printf("%p\n", &ra); // 输出ra的地址
}

👁️‍🗨️输出结果: 

可见 ra 是同一个变量,就像周树人和鲁迅指的是同一个人一样。

❓那我这样用行不行呢?

int a = 10;
char& ra = a; // 定义引用类型

❌可以看到报错: 

也就是说:

引用类型必须和引用实体同种类型

3.特征

int a = 10;
int& ra;

❓直接这样起别名会怎么样?

❌报错:

所以注意 引用在定义时必须初始化。 

像周树人那样有多个笔名,我们也可以起多个别名

int& ra = a;
int& rra = a;

还有一个潜在的特征就是 一个引用只能对应一个实体,就像鲁迅就是指的是周树人。

📝特征总结:

• 引用在 定义时必须初始化;
• 一个变量可以有多个引用;
• 引用一旦引用一个实体,再不能引用其他实体 

4.常引用

什么是常引用呢? 就是面向常量的引用嘛

给常量起别名那些事:

const int a = 10;
int& ra = a; 

❌报错:

也就是说给常量起别名时要用常引用:

const int a = 10;
// int& ra = a; // 该语句编译出错,a为常量
const int& ra = a; // 正确的

倒过来也一样:

int& b = 10; 
const int& b = 10;

❌报错:

const int& b = 10; // 正确的
// int& b = 10; // 该语句编译出错,b为常量
const int& b = 10;

 所以对待常量,引用也要使用常引用。

Part2:使用场景

学了引用,终归是要用于实践的,那么引用的使用场景有哪些呢? 

1.做参数

没错,引用可以像指针那样做参数:
🌰例子:

void Swap(int& x, int& y)
{
	int temp = x;
	y = x;
	y = temp;
}

这是一个经典的交换函数,我们再看看指针版本的:

void Swap(int* x, int* y)
{
	int temp = *x;
	*y = *x;
	*y = temp;
}

但从形式上看,引用版的的却简洁,省去了解引用的步骤。

从效率上来看,也是引用版更胜一筹,因为引用变量不占用内存空间。

📝所以传参时大家可以考虑引用。

2.做返回值

先给你一段代码体会一下:

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

int main()
{
	int& ret = Add(1, 2);
	cout << ret << endl;

	return 0;
}

引用是不占有内存的,直接返回 c 的别名

说到这里,你应该反应过来了,这段代码是有问题的:

❌错误:

① 存在非法访问,当 Add 函数调用结束,栈帧销毁,c 的空间还给操作系统,而 c 的引用被 ret 接收,还是会去访问 c 的空间;

② 如果 Add 的栈帧销毁后,空间被清理,c 取到的就是随机值,紧接着 ret 接收的就是随机值。当然取决于编译器啦 ~

再来看看下面这段代码:

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

int main()
{
	int& ret = Add(1, 2);
	cout << ret << endl;
	Add(10, 20);
	cout << ret << endl;

	return 0;
}

👁️‍🗨️输出结果: 

嘿?我寻思我也没动 ret 啊,怎么变了?

🪄 因为再次调用了 Add 函数,这次调用覆盖了之前已经销毁的栈帧,由于返回的是 c 的引用,而不是 c 本身,所以 ret 被覆盖为最新的值。

这个角度来看,使用引用作返回值条件还挺苛刻。

📝总结:

不要轻易使用引用作为返回值;

如果函数返回时,出了函数作用域,返回对象还 没还给系统 ,则可以使用引用返回,
如果已经还给系统了,就老老实实使用传值返回。

Part3:有关引用的探讨

1.传值,传引用效率比较

❓你可以先考虑下:传值和传引用作参数/返回值,谁的效率更高?

当然是传引用返回

🪄传值作参数/返回值,不是直接传递实参/返回变量,而是传递实参/返回变量的一份临时拷贝,因此直接传递实参/返回变量效率低下,参数/返回值越大越明显。

这里不妨测试一下两者的效率:

#include<iostream>
#include <time.h>
using namespace std;

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 < 100000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++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;
}

这段代码以函数结束时间来表示传参的效率

👁️‍🗨️输出结果: 

 

不难看出,引用传参的效率杠杠滴。

📝总结:

① 传引用作参数/返回值,有些场景下面,可以提高性能(大对象 + 深拷贝对象)。

② 传引用作参数/返回值,输出型参数和输出型返回值。

引用的使用特别多,是学习的重点。

2.引用和指针的区别

在开头的概念中就说到:

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

引用:

int main()
{
	int a = 10;
	int& ra = a;

	cout << "&a = " << &a << endl;
	cout << "&ra = " << &ra << endl;
	return 0;
}

👁️‍🗨️输出结果: 

不过这只是语法的层面上,那底层实现上呢?

我们可以两者汇编代码的区别:

int main()
{
	int a = 10;

	int& ra = a;
	ra = 20;

	int* pa = &a;
	*pa = 20;

	return 0;
}

 转到反汇编:

惊奇的是,反汇编中引用和指针的逻辑操作是相同的!

⚔️所以我们可以得出这样的结论:

引用是按照指针的方式来实现的,在底层实现上是有空间的。

明白了这点之后,再说一下引用和指针的区别: 

① 引用概念上定义一个变量的别名,指针存储一个变量地址;

② 引用在定义时必须初始化,指针没有要求;

 这个好理解的,给变量起别名的前提是给那个变量起呀,而指针就不需要。

③ 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;

④ 没有NULL引用,但有NULL指针;

⑤ sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

⑥ 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小; 

⑦ 有多级指针,但是没有多级引用; 

 指针有一级指针,二级指针,三级指针等,而引用就是标定一个实体,不能再给别名起别名。

⑧ 访问实体方式不同,指针需要显式解引用,引用编译器自己处理; 

 这在一定程度上表明了引用的便利之处,果然C++比C语言省心呐。

⑨ 引用比指针使用起来相对更安全。 

 为什么这么说呢,因为指针当中有野指针,这是一种非常危险的存在,而引用就不会有这样的危险因素。


总结: 

这篇博客从引用的基础概念开始,经历了引用的使用和相关探讨,相信你对引用有了一定的认知,这方面是C++的重点内容,要理解呀。

码文不易 

如果你觉得这篇文章还不错并且对你有帮助,不妨支持一波哦  💗💗💗

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

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

相关文章

最常用的js混淆加密解

JS混淆加密是一种用于保护JS代码的技术&#xff0c;它能够将代码变得难以理解和修改&#xff0c;从而提高代码的安全性。但是&#xff0c;当我们需要修改和维护这些代码时&#xff0c;我们就会面临困难。因此&#xff0c;在某些情况下&#xff0c;我们需要解这些JS代码。 下面…

新唐NUC980使用记录(5.10.y内核):访问以太网(LAN8720A) 启用SSH

文章目录 目的修改内核和设备树以访问以太网制作根文件系统并启用SSH总结 目的 这篇文章主要测试新唐NUC980&#xff08;5.10.y内核&#xff09;访问以太网&#xff08;PHY为LAN8720A&#xff09;以及启用SSH。 这篇文章中内容均在下面的开发板上进行测试&#xff1a; 《新唐…

【三维几何学习】网格可视化-Cube engraving数据集

网格可视化-Cube engraving数据集 引言一、blender二、meshlab三、3D查看器 引言 三角网格(Triangular Mesh)分类数据集 MeshCNN: A Network with an Edge 可参考以上链接深入了解 or 下载数据集。Cube engraving(论文叫法)数据集&#xff0c;又称为Cubes classification datas…

ClickHouse之Explain查看执行计划

文章目录 前言基本语法EXPLAIN 类型EXPLAIN PLANEXPLAIN ASTEXPLAIN SYNTAXEXPLAIN PIPELINEEXPLAIN ESTIMATE 补充忠告 前言 在 clickhouse 20.6 版本之前要查看 SQL 语句的执行计划需要设置日志级别为 trace 才能 可以看到&#xff0c;并且只能真正执行 sql&#xff0c;在执…

快商通联合创始人李稀敏入选“科技专家库专家名单”

3月10&#xff0c;厦门市科学技术局关于2023年第一批拟入选科技专家库专家名单进行公示。经过层层审核&#xff0c;快商通联合创始人李稀敏入选“科技专家库专家名单”。 据了解&#xff0c;厦门市科学技术局的“科技专家库专家名单”是由市科技局精心挑选的一批优秀专家&…

Unsupervised Domain Adaption (UDA)及domain shift介绍

UDA UDA想解决的问题是目标域上数据标签的缺乏&#xff0c;具体而言&#xff0c;存在着源域和目标域&#xff0c;源域上存在大量的标注样本对 D s { ( X i , y i ) } D_s\{(X_i,y_i)\} Ds​{(Xi​,yi​)}&#xff0c;我们可以在上面以有监督的方式训练各种模型&#xff0c;但此…

常见舆情监测系统的分类和特点

随着网络和社交媒体的发展&#xff0c;舆情监测系统逐渐成为企业和政府机构必备的工具之一。舆情监测系统可以帮助企业和政府机构全面了解公众对其品牌、产品、政策等的反应和态度&#xff0c;及时发现和解决问题&#xff0c;提高公信力和形象。本文将介绍常见的舆情监测系统的…

Python Tox

tox其核心作用是支持创建隔离的 Python 环境&#xff0c;在里面可以安装不同版本的 Python 解释器与各种依赖库&#xff0c;以此方便开发者做自动化测试、打包、持续集成等事情。 简单来说&#xff0c;tox 是一个管理测试虚拟环境的命令行工具。 我介绍一种应用场景&#xff…

如何在Jetpack Compose中设置渐变背景

如何在Jetpack Compose中设置渐变背景 只需几步即可通过平滑渐变增强应用程序的用户界面 虽然它经常出现在网络前端的世界中&#xff0c;但渐变背景可以为您的移动应用程序增添专业和美观的触感&#xff0c;使其对您的用户更具吸引力。 第 1 步&#xff1a;创建渐变画笔 为…

Semantic Segmentation using Adversarial Networks

首次将GAN用于语义分割&#xff0c;用于辨别分割图是来自GT还是来自分割网络。作者的想法来自借助GAN可以检测和矫正GT和模型分割图的高阶不一致。最后在Standford和PASCAL VOC 数据集上验证了想法。 对抗学习&#xff1a; 使用两个权重和的混合损失函数进行优化&#xff0c;第…

从win7升级到win10过程中遇到的问题:安装工具无法运行、卸载VMware

目录 1. 概述2. 微软官方安装工具无法运行3. 控制面板的卸载程序里面找不到VMware4. 输入产品密钥5. 安装完后仍然未激活6. 雨林木风 1. 概述 因为新电脑还没有到&#xff0c;把上学时候的笔记本翻出来顶一顶。旧笔记本还是win7&#xff0c;我的鼠标没办法使用&#xff0c;干脆…

HDCTF web复现

[HDCTF 2023]SearchMaster 传data 使用{if}标签闭合达到命令执行的效果 {if phpinfo()}{/if} NSSCTF{f578f8ba-246e-452b-b070-22bc4fc4313d} Smarty模板注入&CVE-2017-1000480 - 先知社区 (aliyun.com) [HDCTF 2023]YamiYami 非预期解 第一个连接 跳转到百度&#xf…

远程访问(内网穿透)

文章目录 介绍cpolar安装使用终端访问远程桌面访问 仅靠ssh&#xff0c;等只能实现同局域网下的服务器访问&#xff0c;本文介绍使用cpolar内网穿透工具实现非同局域网下的访问 介绍 远程&#xff1a;1804 ubuntu 软件依赖&#xff1a;ssh&#xff0c;xrdp&#xff0c; cpolar…

【K8s】资源管理与实战入门

文章目录 一、资源管理1、资源管理介绍2、YAML语言语法3、资源管理方式4、命令式对象管理--kubectl5、命令式对象配置6、声明式对象配置7、报错 二、实战入门1、namespace2、Pod3、Label4、deployment5、Service 一、资源管理 1、资源管理介绍 在kubernetes中&#xff0c;所有…

如何有效的向 AI 提问 ?

文章目录 〇、导言一、Base LLM 与 Instruction Tuned LLM二、如何提出有效的问题 &#xff1f;1. 明确问题&#xff1a;2. 简明扼要&#xff1a;3. 避免二义性&#xff1a;4. 避免绝对化的问题&#xff1a;5. 利用引导词&#xff1a;6. 检查语法和拼写&#xff1a;7. 追问细节…

7天获邀请函|环境科学研究学者持加拿大麦吉尔大学Offer申报CSC

I老师要求2周内获得邀请函且指定加拿大。我们只用了7天时间就获得加拿大排名榜首的麦吉尔大学邀请函&#xff0c;整整提前了一半时间&#xff0c;效率奇高。 I老师背景&#xff1a; 申请类型&#xff1a;CSC访问学者 工作背景&#xff1a;某研究所研究人员 教育背景&#xf…

g++编译静态库与动态库

该文目的是基本理清一个在linux在c静态库与动态库的编译和使用 一个非常基础的一节&#xff0c;简单的整合了一下目前已有的文章 前提准备&#xff1a; 文件: touch SoDemoTest.h one.cpp two.cpp three.cpp main.cpp代码 /* SoDemoTest.h */ #ifndef _SO_DEMO_TEST_HEADE…

【Ubuntu22.04】内网部署Ubuntu Server 22.04.2

镜像下载 方式一&#xff1a;官网下载 https://ubuntu.com/download/server 方式二&#xff1a;清华镜像站 https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/22.04.2/ 方式三&#xff1a;百度网盘 链接: https://pan.baidu.com/s/1g24PDfAiPVsxMm7DVpERdg?pwd1020 …

myql的三种删除方式:delete truncate drop

前言 在 MySQL 中&#xff0c;删除的方法总共有 3 种&#xff1a;delete、truncate、drop&#xff0c;而三者的用法和使用场景又完全不同&#xff0c;接下来我们具体来看。 1.delete detele 可用于删除表的部分或所有数据&#xff0c;它的使用语法如下&#xff1a; delete …

独立产品灵感周刊 DecoHack #052 - 100个AI 工具导航网站

本周刊记录有趣好玩的独立产品设计开发相关内容&#xff0c;每周发布&#xff0c;往期内容同样精彩&#xff0c;感兴趣的伙伴可以 点击订阅我的周刊。为保证每期都能收到&#xff0c;建议邮件订阅。欢迎通过 Twitter 私信推荐或投稿。 ❤️ 刚换工作再加上个人原因有些自己的事…