【C++】右值引用(极详细版)

news2025/1/17 23:01:11

在讲右值引用之前,我们要了解什么是右值?那提到右值,就会想到左值,那左值又是什么呢?

我们接下来一起学习!


 

目录

1.左值引用和右值引用

1.左值和右值的概念

2.左值引用和右值引用的概念

2.左值引用和右值引用引出

3.右值引用的价值

1.补齐左值引用的短板——函数传返回值时的拷贝

1.移动构造

2.移动赋值 

2.对于插入右值数据时,也可以减少拷贝

4.万能引用和完美转发

1.万能引用

总结


1.左值引用和右值引用

1.左值和右值的概念

左值准确来说是:一个表示数据的表达式(如变量名或解引用的指针),且可以获取他的地址(取地址),可以对它进行赋值;它可以在赋值符号的左边或者右边。

右值准确来说是:一个表示数据的表达式(如字面常量、函数的返回值、表达式的返回值),且不可以获取他的地址(取地址);它只能在赋值符号的右边

右值也是通常不可以改变的值。

具体我们举例来了解:

int main()
{
	// 以下的a、p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	int a = b;
	const int c = 2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
}

2.左值引用和右值引用的概念

那么我们就可以很容易地知道: 

左值引用:给左值取别名

右值引用:给右值取别名

需要注意的是:左值引用只能引用左值;const左值引用可以左值,也可以引用右值(因为右值通常是不可以改变的值,所以用const左值引用是可以的);右值只能引用右值;左值可以通过move(左值)来转化为右值,继而使用右值引用。const右值引用是怎么个事儿呢?(这里要埋伏笔,先不讲)

int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a;   // ra1为a的别名
	//int& ra2 = 10;   // 编译失败,因为10是右值

	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;

	 //右值引用只能右值,不能引用左值。
	int&& r1 = 10;


	int a = 10;
    //message : 无法将左值绑定到右值引用
	int&& r2 = a;


	 //右值引用可以引用move以后的左值
	int&& r3 = std::move(a);

	return 0;
}

此时我们已经了解了左值和左值引用,右值和右值引用。所以可以发现,左值引用就是我们通常使用的引用。那么左值引用和右值引用的意义或者区别在哪里呢?我们继续往下看。 


2.左值引用和右值引用引出

左值引用的意义在于:

1.函数传参:实参传给形参时,可以减少拷贝。

2.函数传返回值时,只要是出了作用域还存在的对象,那么就可以减少拷贝。

但是左值引用却没有彻底的解决问题:函数传返回值时,如果返回值是出了作用域销毁的(出了作用域不存在的),那还需要多次的拷贝构造,导致消耗较大,效率较低。

所以这也就是为什么出现了右值引用,当然这是是右值引用价值中的一个!

那在没有右值引用之前,我们是如何解决函数传返回值的拷贝问题呢?通过输出型参数

//给一个数,去构建一个杨辉三角

//如果是函数返回值去解决,那么拷贝消耗是非常大的
vector<vector<int>> generate(int numRows) {
	vector<vector<int>> vv(numRows);
	for (int i = 0; i < numRows; ++i)
	{
		vv[i].resize(i + 1, 1);
	}

	for (int i = 2; i < numRows; ++i)
	{
		for (int j = 1; j < i; ++j)
		{
			vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
		}
	}
	return vv;
}

//所以在没有右值引用之前,我们可以通过 输出型参数来解决这个问题
void generate(int numRows,vector<vector<int> vv) {
    vv.reserve(numRows);
	for (int i = 0; i < numRows; ++i)
	{
		vv[i].resize(i + 1, 1);
	}

	for (int i = 2; i < numRows; ++i)
	{
		for (int j = 1; j < i; ++j)
		{
			vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
		}
	}
	return vv;
}

当然这种方法还是有局限性的,而且平时也不会经常使用,所以很有必要去了解右值引用的强大解法!!

3.右值引用的价值

1.补齐左值引用的短板——函数传返回值时的拷贝

那接下来上实例:

我们用自己实现string类来观察会更加清晰:

namespace mj
{
	class string
	{
	public:
		typedef char* iterator; 
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		// 移动构造
		string(string&& s)
		{
			cout << "string(const string& s) -- 移动拷贝" << endl;

			swap(s);
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string s) -- 移动赋值" << endl;
			swap(s);

			return *this;
		}
	

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};

	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

		mj::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;

			str += ('0' + x);
		}

		if (flag == false)
		{
			str += '-';
		}

		std::reverse(str.begin(), str.end());
		return str;
	}
}

int main()
{
    //拷贝构造
    mj::string ret=mj::to_string(-1234567);

    //赋值拷贝
    mj::string ret;
    ret=mj::to_string(-1234567);

    return 0;
}

1.移动构造

我们用to_string()函数的返回值来构造ret对象,这就涉及到了函数传返回值时的拷贝问题

1.正常构造的过程:

但是编译器会自动优化(连续的构造,但是不是所有的情况都优化),将两个拷贝构造优化为一个拷贝构造,直接跳过中间的临时变量:

但是对于自定义类型时,虽然将两次拷贝构造优化为一次,拷贝构造仍然要消耗很大的空间,所以这时右值引用的第一个价值就要登场!

右值引用来补齐函数传返回值时的拷贝短板:

当调用拷贝构造时,之前我们只有传左值,进行深拷贝,完成拷贝构造;

但现在我们有了右值,可以传右值,那么传右值的拷贝构造是怎么搞的呢?

再举一个例子:

右值分为:纯右值(字面常量)和将亡值(更侧重于自定义类型的函数的返回值,表达式的返回值)。

当构造传左值,就走拷贝构造,当构造传右值,就走移动构造。

对于左值,我们后续还要使用,所以只能进行深拷贝,完成拷贝构造。

但对于右值(将亡值),可以直接进行资源的交换,将this和将亡值交换资源。

所以,回到函数传返回值的问题:

在 有了移动构造以后,再经过编译器的优化,就可以做到直接移动构造(资源的交换),实现0拷贝,效率极高!!

2.移动赋值 

第一种情况是针对拷贝构造的情况,接下来是针对赋值拷贝的情况:

赋值拷贝同理可得:

 

这里运行后,我们看到调用了一次移动构造和一次移动赋值。
因为如果是用一个已经存在的对象接收,编译器就没办法优化了。mj::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为mj::to_string函数调用的返回值赋值给ret,这里调用的移动赋值。(直接资源交换)

总结:


2.对于插入右值数据时,也可以减少拷贝

只有左值引用时的插入接口:

STL容器插入接口函数也增加了右值引用版本:

会直接进行资源交换,将将亡值和新创建的节点中的数据进行资源交换。


4.万能引用和完美转发

讲到这里,我们埋的伏笔也就要出来了:有左值引用,const左值引用;右值引用,但却没有提到const右值引用。

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址。(右值被右值引用以后就成为了左值)
例如: 不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。
int main()
{
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	const double&& rr2 = x + y;

	rr1++;
	//rr2++;  //不可以修改

	cout << &rr1 << endl;
	cout << &rr2 << endl;

	return 0;
}

当然这个的具体应用场景在这里:

例如:

这里的移动构造和赋值构造,如果参数设为右值引用,那么作为右值如果不可以被修改,那资源的交换就不可以进行,所以这就是为什么,右值引用右值以后,就成为了左值。

情况二:

在我们自己模拟实现的list中,也实现插入接口是右值引用:

这就是在传右值时,右值引用会改变右值的特性,将其变为左值,那么需要不断move(左值)。

所以我们会想,有没有这么一个东西,自动去识别我们传的参数是左值还是右值,不会因为右值引用而改变右值属性。我们继续往下看

1.万能引用

当并不明确规定传右值或者左值时:

 万能引用在这里起到了用处,可以随便传。(也叫做折叠)模板中的&&不是右值引用,而是为了万能引用,可以折叠。当传左值时,就把两个&&折叠为一个。同理可得

但是在继续调用Fun时,还是会因为属性导致结果并不是我们需要的:

走到调用fun(t)时,还是会因为右值引用导致右值变为左值,所以又出来了完美转发:

template<typename T>
void PerfectForward(T&& t)
{
	// t可能是左值,可能是右值
	//Fun(move(t));

	// 完美转发,保持他属性
	Fun(std::forward<T>(t));
	//t++;
}

 很好的保持了属性。

所以在这里:

 


总结

右值引用的两个价值;

万能引用和完美转发

我们下期再见!

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

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

相关文章

C++linux高并发服务器项目实践 day2

Clinux高并发服务器项目实践 day2 静态库的制作静态库命名规则静态库的制作 动态库的制作命名规则制作使用动态库与静态库的区别解决动态库连接失败问题静态库和动态库的对比静态库的优缺点动态库的优缺点 Makefile什么是MakefileMakefile文件命名和规则Makefile的使用工作原理…

SpringSpringBoot常用注解总结

0.前言 可以毫不夸张地说&#xff0c;这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法&#xff0c;掌握搞懂&#xff0c;使用 SpringBoot 来开发项目基本没啥大问题了&#xff01; 为什么要写这篇文章…

【分享】Excel表格的密码忘记了怎么办?附解决办法

我们知道通过设置密码可以保护Excel表格&#xff0c;可有时候设置后很久没用就把密码忘记了&#xff0c;而Excel并没有找回密码的选项&#xff0c;那要怎么办呢&#xff1f;今天小编就来分享一下忘记Excel密码的解决方法。 Excel表格可以设置多种密码&#xff0c;不同密码对应…

短视频平台-小说推文(Lofter)推广任务详情

​Lofter日结内测中&#xff0c;可能暂只对部分优质会员开放! 注意 Lofter 关键词7天未使用&#xff0c;可能会被下线。 Lofter 不再需要回填视频链接了。 接Lofter官方通知 关于近期部分博主反馈播放量高但搜索量很低的问题尤其是快手平台&#xff0c;我们做了代码、服务器…

No.040<软考>《(高项)备考大全》【第24章】成熟度模型

【第24章】成熟度模型 1 考试相关2 第一维四个阶梯3 项目成熟度模型OPM3CMMI过程域 4 成熟度级别级别区别 5 练习题参考答案&#xff1a; 1 考试相关 选择可能考0-1分&#xff0c;案例论文不考。 2 第一维四个阶梯 3 项目成熟度模型OPM3 CMMI过程域 CMMI过程域可以分为4类&a…

智能对话机器人Rasa学习资料

文章目录 背景收集的Rasa学习资料官网B站其他 类似产品教学机器人售后咨询效果手机推荐效果 背景 最近做了一个Ros2项目&#xff0c;界面如下图&#xff1a; 客户要求能够使用语音快速执行特定动作如:打开视频窗口、显示小车1视频、无人机1返航等&#xff0c;这就涉及到了自然…

C++ : 整体工程构架设计流程

重点&#xff1a; 1.一个项目通常分为bin(存放项目生成的dll和整体工程的exe)&#xff0c;code(存每个项目的代码)&#xff0c;lib(存每个项目生成的lib),pdb(存放项目生成的pdb文件)&#xff0c;sln(解决方案) 整体创建流程&#xff1a; 一个主干项目&#xff0c;其他若干依赖…

Java接口自动化测试框架系列:提升测试效率的自动化测试框架

目录&#xff1a;导读 一、什么是自动化测试 二、自动化测试的缺点 三、自动化测试框架选型 原则 对比 四、框架构建 【自动化测试工程师学习路线】 一、什么是自动化测试 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。 通常&#xff0c;在设计了测试…

【UE】暂停游戏界面及功能实现

效果 步骤 1. 首先在项目设置中添加一个暂停的操作映射 2. 新建一个控件蓝图&#xff0c;命名为“PauseMenuWidget” 3. 打开“ThirdPersonCharacter”&#xff0c;添加一个布尔类型变量&#xff0c;命名为“isScreenShow”&#xff0c;用于判断当前玩家是否打开了暂停界面 在…

【Linxu网络服务】DHCP

DHCP 一、DHCP工作原理1.1背景1.2优点1.3 DHCP分配方式1.4DHCP工作原理 二、使用DHCP动态配置主机地址2.1实验一&#xff1a;动态配置主机地址2.2给Linux客户机配置动态地址**2.4设置一个外网口&#xff0c;给客户端设置一个固定的ip地址 一、DHCP工作原理 作为服务端负责集中…

uniapp 之 将marker 渲染在地图上 点击弹层文字时显示当前信息

目录 效果图 总代码 分析 1.template 页面 地图显示代码 2. onload ①经纬度 ②取值 ③注意 ④ 3.methods ① 先发送 getStationList 请求 获取 数组列表信息 ② regionChange 视野发生变化时 触发 分页逻辑 ③ callouttap 点击气泡时触发 查找 当前 marker id 等…

基于第一性原理DFT密度泛函理论的计算项目

随着计算机技术的不断发展&#xff0c;计算材料科学的方法也日益成熟。其中&#xff0c;基于第一性原理的密度泛函理论&#xff08;DFT&#xff09;计算方法&#xff0c;因其准确性、可靠性和高效性而广受欢迎。本文将介绍基于DFT的密度泛函理论的计算项目&#xff0c;包括电子…

云内基于 SRv6 的 SFC 方案

1. 基于 SRv6 的 SFC 服务链 为满足用户的业务数据安全、稳定等需求&#xff0c;提供各种基础保障或增值优化服务&#xff0c;在传统网络中&#xff0c;经常使用业务功能节点&#xff08;如负载均衡、防火墙等&#xff09;实现服务供应。但这些业务功能节点往往与网络拓扑和硬件…

Fortinet Accelerate 2023全球网安大会成功举办 加速推进网络安全行业融合与整合

近日&#xff0c;Fortinet全球网络安全大会——Fortinet Accelerate 2023 在美国奥兰多成功举办。在对企业数字化转型挑战及网络威胁趋势等行业热点进行深入探讨的同时&#xff0c;Fortinet全新发布了以融合与整合为核心设计理念的增强型产品和服务&#xff0c;帮助企业从容应对…

第2章 时间空间复杂度计算

1时间复杂度计算 时间复杂度是什么&#xff1f; 一个函数&#xff0c;用大O表示&#xff0c;例如&#xff1a;O(1), O(N), O(logN). 定性描述算法的运行时间。 时间复杂度常见图&#xff1a; 案例&#xff1a; O(1) let i 0 i 1 解释&#xff1a;每次执行这段代码&#…

【Paper Note】Video Swin Transformer

Video Swin Transformer 介绍架构3.2 3D Shifted Window based MSA Module3.2.1 在不重叠的三维窗口上的MSA3.2.2 3D Shifted Windows3.2.3. 3D Relative Position Bias 3.3 Architecture Variants3.4 Initialization from Pre-trained Model 总结 文章链接&#xff1a;https:/…

zabbix自动发现和自动注册部署

目录 zabbix自动发现 确保客户端上的zabbix-agent2服务状态正常 在web页面删除原有的客户端主机 在服务端和客户端上配置 hosts 解析 在 Web 页面配置自动发现 zabbix自动注册 环境准备 修改 zabbix-agent2 配置文件 在 Web 页面配置自动注册 zabbix自动发现 对于agen…

如何使用Git将本地代码上传GitHub仓库?

如何使用Git将本地代码上传GitHub仓库呢&#xff1f; 前提 要上传本地代码到GitHub仓库&#xff0c;那必然要先在GitHub上建立一个存储代码的仓库&#xff0c;这里我在仓库新建了一个名为5blog的仓库。 备注&#xff1a;本文章将以默认分支main为例来讲解上传步骤 接着我们打…

学习经验分享【26】论文写作画图方法(持续更新)

写作前面&#xff1a;论文投稿能否成功&#xff0c;图表作为比较直观的展现&#xff0c;起着关键的作用&#xff0c;图表丰富规范好看&#xff0c;一定程度上能够吸引编辑和审稿人的眼球&#xff0c;提升录用概率。就跟人的形象一样&#xff0c;形象好第一印象就会好&#xff0…

简单的重装系统教程

郁闷&#xff0c;最近电脑一直蓝屏重启&#xff0c;用 2 分钟就蓝屏一次&#xff0c;遂产生重装系统的想法。 准备 U盘(8G或以上) PE 工具&#xff1a; 微PE工具箱快速指引 | 微PE优盘使用说明书 (wepe.com.cn) 系统镜像&#xff1a; 官网 Windows 10 官网 Windows 11 M…