【详解C++中的引用】

news2025/2/28 20:22:22

文章目录

  • 一、什么是引用
    • 二、引用规则
      • 三、引用特性
        • 四、使用场景
          • 1.做函数参数
          • 2.做返回值
            • 五、常引用
    • ps:为什么类型转换会产生临时变量?
  • 六、引用和指针的区别
  • 总结


一、什么是引用

引用就是给一个变量取别名。

注意:这个引用不会新开辟一块空间,而是和原来的变量公用一块空间。

举个例子:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
在这里插入图片描述

二、引用规则

引用规则:引用实体类型+&+引用别名 = 引用实体。

比如下面:

int main()
{
	int a = 10;
	//引用
	int& ba = a;
	
	ba = 20;
	printf("%d ", a);

	return 0;
}

上面代码为例:

引用对象类型是int + & + 引用别名(ba) = 引用对象(a)

C++中的 “&”符号跟类型在一起是不在是取地址,而是”引用“。

现在ba这个就是a的别名,和a是同一块内存空间,改变了ba的内容,就等于改变了a的内容。

同时,一个变量可以有多个引用。

相当于一个人可以有多个别名一样。

就像是:假如你在家被叫做小红,在外面被叫燕燕。你的妈妈叫小红吃饭,然后小红去吃饭了,那燕燕是不是也吃了,你是不是也吃了呢?

三、引用特性

1.引用类型必须是和引用实体是同一类型。

比如:
在这里插入图片描述
报错的原因是:引用对象的类型和引用实体的类型不一致。

引用的对象a是int类型,而给它取别名却是double类型,这是不允许的。

2.引用在定义时必须初始化

比如:

在这里插入图片描述
这也是不允许的。

3. 引用一旦引用一个实体,就不能引用其他实体。

比如:

int main()
{
	int a = 10;
	//引用
	int& pa = a;

	int x = 20;
	pa = x;

	return 0;
}

这段代码,有错误吗?

没有错误,但是pa = x这行代码,并不是引用更改,因为前面已经提过:

引用一旦引用一个实体,就不能引用其他实体。

所以这行代码的意思是:将x 的值赋值给pa。

此时a的值是20了。

四、使用场景

1.做函数参数

引用可以做函数的参数,作用相当于指针,可以改变变量本身。

比如:

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a = %d ,b = %d\n", a, b);
	Swap(a, b);
	printf("交换后:a = %d ,b = %d\n", a, b);
	return 0;
}

传参传一个实参过去,但是接收时使用引用,此时该引用就是形参的别名,形参的别名改变会改变形参本身。

这个是使用引用一个好的地方。

做返回值的作用:
1.做输出型参数,节省空间。
我们传形参时,用引用接收,也不再需要使用地址,不再需要开辟指针空间来接收,直接改变引用即可改变形参。

2. 提高效率,当传过来的形参是大对象/深拷贝对象时,能够极大地提高效率。

2.做返回值

先看不用引用做返回值,用普通的返回值:


案例1,错误代码
int test()
{
	int n = 10;
	n++;

	return n;
}

int main()
{
	int ret = test();

	printf("%d\n", ret);
	return 0;
}

这样的返回值是我们常见的返回值。
这里需要提一点:
test函数并不是直接返回n的。

因为test函数在调用结束后会销毁它所在的栈帧,连同n会一起销毁,所以编译器会先保存n的值到一个寄存器中,再销毁栈帧,然后返回寄存器的值给ret。

过程如下:
在这里插入图片描述
所以就出现了一个问题。

当我们用上面的代码,返回的是n的引用(别名)时,这就不安全了。因为返回的是n的引用,不会创建临时空间给n,而是直接返回n。 但是返回之后n所在的函数栈帧会被销毁,所以连同n一起销毁了,但是此时ret是n这块已经不属于自己的空间的拷贝,所以ret是违法的。

打印出来的ret,可能是随机值,也可能是n原来的值,但如果是n原来的值,这只是侥幸,因为n原来的空间暂时没有被使用。但如果n这块空间被其他函数使用了,此时ret就有可能是随机值。

所以在上面的例子中,不能使用引用来返回。

再看下面的例子:

案例2 ,正确代码
int& test()
{
	static int n = 10;
	n++;
	return n;
}

int main()
{
	int ret = test();
	printf("%d\n", ret);
	return 0;
}

注意,n是被static修饰过的变量。此时可以用引用进行返回了,因为函数test的销毁不会销毁n,n是在静态区开辟的空间,而函数是在栈区开辟的空间,两者互不影响。

返回n的引用是绝对安全的。

再看下面:

案例3,错误代码
int& test()
{
	int n = 10;
	n++;
	return n;
}

int main()
{
	int& ret = test();
	printf("%d\n", ret);
	return 0;
}

此时与案例二相似,但是n不是被static修饰过的,而且ret也是引用,相当于返回n的引用后,再用引用接收n的引用,此时ret也还是n的别名,而n是在栈区开辟的空间,销毁后,此时n的空间不再属于自己,打印ret,相当于打印不属于自己的n,这是违法的行为。

当这段栈空间被其他东西用之后,n的值可能是随机值了。
在这里插入图片描述

再看下面的案例:

案例4,正确代码
int& test()
{
	static int n = 10;
	n++;
	return n;
}

int main()
{
	int& ret = test();
	printf("%d\n", ret);
	return 0;
}

此时n 是被static修饰过的,所以栈帧空间的销毁不会影响n,打印ret(ret是n的引用),会正确打印出来。

总结:
1.使用引用做返回值时,引用返回不会开辟临时空间保存返回值
2.而不管改变量是在栈区还是在静态区,不用引用都会开辟临时空间保存返回值。
但是使用引用必须保证返回值是绝对的安全。

五、常引用

1.在引用的过程中,权限不能放大,只能缩小或平移。
1.在引用的过程中,权限不能放大,只能缩小或平移。
1.在引用的过程中,权限不能放大,只能缩小或平移。

案例1:
错误示例

int main()
{
	//权限不能放大,不正确
	const int a = 0; // 常变量,不可修改
	int& b = a; // 起了一个别名,必须也是常引用
	
	return 0;
}

a是一个被const修饰后的常变量,不可修改。
而b是a的引用,此时b并没有被const修饰,意味着b可以修改,但是a已经不能被修改了,b是a的别名同样不可修改,这是规定。

所以权限不能放大

案例2:
正确实例

int main()
{
	const int a = 0; // 常变量,不可修改
	//权限的平移
	const int& b = a;
	return 0;
}

变量a 和引用b都被const修饰了,它们的权限是一样的,所以权限可以平移。

案例3:
正确案例:

int main()
{
	//权限可以缩小
	int g = 0;
	const int& h = g;
	return 0;
}

g是一个变量,h是g的引用,但是h被const修饰后,意味着h不能修改。
所以这是一个权限的缩小。

2.只要发生类型转换,都会产生临时变量。
而临时变量不可修改,具有常性。

2.只要发生类型转换,都会产生临时变量。
而临时变量不可修改,具有常性。

2.只要发生类型转换,都会产生临时变量。
而临时变量不可修改,具有常性。

类型转换包括:隐式类型转换,截断,整型提升等。

比如:

int main()
{
	double d = 1.1;
	int a = d;

	return 0;
}

这里会发生隐式类型转换。d是double类型拷贝给a ,会在中间生成一个int类型的临时变量,然后把d放入该临时变量中,然后该临时变量再拷贝给a。所以把d拷贝给a是不会改变d本身的。
在这里插入图片描述
再看下面的代码:

错误示例
int main()
{
	double d = 1.1;
	int& a = d;
	return 0;
}

为什么a不能作为d的引用?
本质上还是权限放大的问题。
d是double类型的变量,a是int类型的引用,中间创建一个int类型的临时拷贝,而临时拷贝具有常性,给了a之后,a是引用,不具有常性,就相当于权限的放大。

正确示范
int main()
{
	double d = 1.1;
	const int& a = d;
	return 0;
}

加了const修饰后,引用a就具有了常性,相当于权限的平移了。

ps:为什么类型转换会产生临时变量?

看下面的例子:

int main()
{
	int a = 1;
	double b = 1.1;
	if (b > a)
	{
		cout << "hehe" << endl;
	}
	return 0;
}

此时会打印hehe,因为表达式左右两边如果类型不同,会发生整型提升或截断。

这里b和a相比,a会发生整型提升到double类型再与b比较。

而整型提升的过程会生成一个临时变量,这个临时变量就是a提升后的结果(并不是a本身提升)。

所以比较前后a和b的值都不会发生改变。

六、引用和指针的区别

从汇编的角度看,引用的底层也是用指针实现的。
在这里插入图片描述

但是在语法层面,引用不开空间,指针会开辟一块空间。

总结

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
int main()
{
	int a = 10;
	//指针存储a的地址
	int* pa = &a;
	
	//b是a的引用(别名)
	int& b = a;
	return 0;
}
  1. 引用在定义时必须初始化,指针没有要求。
int main()
{
	int a = 1;

	//引用必须初始化
	int& b = a;

	//指针可不初始化
	int* p;
	return 0;
}
  1. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
int main()
{
	int a = 10;
	int b = 20;
	int& d = a;

	//指针可以改变指向的对象
	int* p = &a;
	p = &b;

	//但是引用不可改变指向的对象
	//这个并不是改变引用d的实体对象,而是把b拷贝给d
	d = b;
	return 0;
}
  1. 没有NULL引用,但有NULL指针。
int main()
{
	//可以存在空指针
	int* p = NULL;
	//不存在空引用,引用必须初始化一个实体
	//错误代码
	int& b;
	return 0;
}
  1. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
int main()
{
	double a = 10;
	
	double* p = &a;
	cout << sizeof(p) << endl;

	double& b = a;
	cout << sizeof(b) << endl;
	return 0;
}

在这里插入图片描述

  1. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
    在这里插入图片描述

  2. 有多级指针,但是没有多级引用。

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

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

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

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

相关文章

微信小程序自定义组件:组件间通讯

前言 略 组件间通信 组件间的基本通信方式有以下几种&#xff1a; WXML 数据绑定&#xff1a;用于父组件向子组件的指定属性设置数据&#xff0c;仅能设置 JSON 兼容数据&#xff08;自基础库版本 2.0.9 开始&#xff0c;还可以在数据中包含函数&#xff09;。具体在 组件模…

Mybatis-plus 两种分页方法(分单表和联表)

mybatis-plus分别使用 一、PageHelper插件分页使用 准备1&#xff1a;引入PageHelper依赖 <!-- pagehelper 分页插件 --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId>…

面试高频代码题

文章目录 链表1.删除有序链表中的重复链表2.删除有序链表的重复数组并只保留只出现过一次的结点3. 无序单链表升序排列 数组1. 冒泡排序2. 折半查找3. 快排4.给1001个数&#xff0c;有一个是重复的&#xff0c;如何不使用额外空间找出来这个数&#xff1f;&#xff08;微软&…

智能文案改写工具-智能改写工具免费

智能写作机器人 智能写作机器人&#xff0c;这是一种让人类写作变得更加简单的创新技术。它的出现&#xff0c;为内容生产领域带来了巨大的进步&#xff0c;不仅提高了人们的写作效率&#xff0c;还让优质的内容更容易被产生和共享。现在&#xff0c;让我们来了解一下智能写作…

Spring Security --- formLogin配置

目录 环境准备 配置自定义登录表单页面 配置登录成功的跳转页面方式 配置登录失败的跳转页面方式 前端表单参数获取 CustomWebSecurityConfigurerAdapter配置类代码示例 环境准备 创建springboot项目引入spring security框架引入thymeleaf模板引擎 配置自定义登录表单页面…

TryHackMe-Year of the Pig(Linux渗透测试)

Year of the Pig 有些猪会飞&#xff0c;有些有故事要讲。开始吧&#xff01; 端口扫描 循例nmap Web枚举 进入80 gobuster扫 进到/admin&#xff0c;尝试弱口令&#xff0c;给出了密码提示 密码本身的一些很简单的单词&#xff0c;密码的后三位是两位数字加一个特殊字符 我…

Flask(Jinja2)服务端模板注入漏洞(SSTI)整理

整理一下Flask框架下的SSTI漏洞相关知识&#xff1a; 漏洞原理 Flask是一个很常用的python框架&#xff0c;其中存在SSTI漏洞。 SSTI&#xff0c;服务端模板注入&#xff0c;很早就知道这个东西&#xff0c;但没有仔细整理过&#xff0c;作为一种注入漏洞&#xff0c;简单说…

LeetCode算法小抄-- 最近公共祖先 和 完全二叉树的节点个数

LeetCode算法小抄-- 最近公共祖先 和 完全二叉树的节点个数 最近公共祖先[236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/)[235. 二叉搜索树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-b…

redis单机最大并发量

redis单机最大并发量 布隆过滤器多级缓存客户端缓存应用层缓存Expires和Cache-Control的区别Nginx缓存管理 服务层缓存进程内缓存进程外缓存 缓存数据一致性问题的解决引入多级缓存设计的时刻 Redis的速度非常的快,单机的Redis就可以⽀撑 每秒十几万的并发,相对于MySQL来说,性…

Linux中将Python2升到Python3

目录 1、安装依赖包 2、下载python3 方式一 方式二 3.解压文件 4.安装 5.建立软连接 1、安装依赖包 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-dev…

Mysql·分库分表

Mysql分库分表 在mysql中新建数据库用以表分库分表mycat解压后配置文件参数server.xml 主要配置mycat服务的参数&#xff0c;比如端口号&#xff0c;myact用户名和密码使用的逻辑数据库等rule.xml 主要配置路由策略&#xff0c;主要有分片的片键&#xff0c;拆分的策略&#xf…

Elasticsearch:Elasticsearch 容量规划

Elasticsearch 是一个可扩展的分布式系统&#xff0c;可为企业搜索、日志聚合、可观察性和安全性提供解决方案。 Elastic 解决方案建立在一个单一、灵活的技术堆栈之上&#xff0c;可以部署在任何地方。 要在自托管或云端运行生产环境 Elasticsearch&#xff0c;需要规划基础架…

删除注册表配置后Chrome仍然显示“由贵单位管理”解决办法

这个提示并不影响使用&#xff0c;但是强迫症看着就是难受&#xff0c;搞掉他。 现象 找到是谁触发的 浏览器输入并打开chrome://policy/ 其实看不看意义不大&#xff0c;因为你知道了也奈何不了他。 解决问题 使用百度搜索一下都是提供的删除注册表的方法&#xff08;通过…

【C++技能树】NameSpace --命名空间的使用

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

burpsuite抓包数据分析

以抓到的pikachu的数据为例&#xff1a; POST /pikachu/vul/burteforce/bf_form.php HTTP/1.1 # POST请求 被抓包的文件 http协议是1.1 Host: www.xxx.com #Host是主机 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101Firefox/49.0 #user-agent是…

【C陷阱与缺陷】两道“有趣”的代码题

如果读者不了解函数指针的&#xff0c;可以先看看这篇文章 链接 < 第一题 > 代码&#xff1a; (*(void (*)())0)();解析&#xff1a; &#x1f4ac;如果你是头一次看上面这段代码的话&#xff0c;心里一定是一个大大的问号&#xff1f;&#xff1f;&#xff1f;现在我…

C++的输入输出概述

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C &#x1f525;座右铭&#xff1a;“不要等到什么都没有了&#xff0c;才下…

前缀和2:2615等值距离和

2615. 等值距离和 - 力扣&#xff08;LeetCode&#xff09; 做这道题之前&#xff0c;先完成1685. 有序数组中差绝对值之和 - 力扣&#xff08;LeetCode&#xff09; 一般性的&#xff0c;我们能在这类题目中总结出以下规律&#xff1a; 求解有序数组中每个元素与q的差值res时…

Traceroute的原理及应用挑战

1 Traceroute简介 Traceroute 是继 ping 之后使用最广泛的网络诊断工具之一&#xff0c;因为它简单且应用范围非常广泛。 traceroute 的可能应用范围从简单的网络错误诊断到揭示底层网络拓扑的大型扫描。然而&#xff0c;由于 traceroute 不是在考虑现代网络技术的情况下构建的…

让你轻松入门APP自动化测试:UiAutomator2框架基础解析

目录 前言 一、uiautomator/uiautomator2的前生今世 1.官方文档介绍 2.梳理一下脉络 3.三款框架对比 二、uiautomator2简介 1.项目组成 2.工作原理 三、环境搭建 1.安装uiautomator2 2.初始化设备 3.init时都干了啥&#xff1f; 四、基础操作 1.连接设备 2.命令…