【C++11】深入理解与应用右值引用

news2024/11/16 4:43:50
🔥 个人主页:大耳朵土土垚
🔥 所属专栏:C++从入门至进阶

这里将会不定期更新有关C/C++的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

文章目录

  • 1.左值引用
  • 2.右值引用
  • 3.右值引用与左值引用的比较
  • 4. 右值引用使用场景和意义
  • 5.结语


1.左值引用

  在C++中,左值引用和右值引用是用来声明变量的引用类型的两种方式。传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以我们将C++11之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

例如:

int a = 10;
int& b = a;	//左值引用

func(int& x)//左值引用
{
//...
}

我们之前学过对变量取别名和函数的传引用传参都是左值引用

  • 什么是左值?

  左值是一个表示数据的表达式(如变量名或/解引用的指针),我们可以获取它的地址对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址

例如,以下都是左值:

int* p = new int(0);
int b = 1;
const int c = 2;

简单来说,左值就是可以取地址的表达式,以上*p,b,c都可以取地址

  • 什么是左值引用?

  左值引用就是对左值的引用,就是给左值取别名。

  例如:

// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

2.右值引用


  右值引用使用&&符号进行声明,它是对一个右值进行引用。

  右值(Rvalue)是指可以放在赋值运算符右侧的表达式,因为它没有一个确定的内存地址,所以右值也不能进行取地址。右值通常是一个临时的值或者一个将要被移动的值。右值可以用于初始化一个变量、传递给函数或者返回给调用者。

右值具有如下特点:

  1. 右值可以是字面量、临时对象、表达式的结果或者一些函数的返回值。
  2. 右值没有持久性,它们不能出现在赋值运算符的左侧。
  3. 右值可以被移动、交换或者销毁。

例如:

double x = 1.1, y = 2.2;
// 以下几个都是常见的右值

10;//字面常量
x + y;//表达式的结果
fmin(x, y);//函数的返回值,也指临时对象

// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);

// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;

结果如下:


需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,例如:上面例子中,不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

✨✨我们不能简单通过在赋值符号的左右来判断左值还是右值,因为左值也可以放在赋值符号的右边,但是我们可以通过是否可以取地址来判断左值和右值,左值可以取地址,右值不可以取地址。


3.右值引用与左值引用的比较


  • 对于左值引用:

  左值引用不可以直接引用右值,但是可以通过const修饰左值引用来引用右值

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


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

上述例子中,10具有常性,不能直接被左值引用,但是加了const之后就可以;右值的字面量、临时对象、表达式的结果或者一些函数的返回值都具有常性,所有被const修饰后可以左值引用右值。

  • 对于右值引用:

  右值引用不可以直接引用左值,但可以通过move(左值)来达到右值引用

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

// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int a = 10;
int&& r2 = a;


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

简单来说move可以将左值转换为右值,这样就可以对左值进行间接地右值引用

✨左值引用总结:

  • 左值引用只能引用左值,不能引用右值。
  • 但是const左值引用既可引用左值,也可引用右值。

✨右值引用总结:

  • 右值引用只能右值,不能引用左值。
  • 但是右值引用可以move以后的左值。

4. 右值引用使用场景和意义


  这里有一个string类,放在自己的命名空间内,和之前实现过的string类一样,用来当作辅助理解的例子:

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

namespace tutu
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			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)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			reserve(s._capacity);
			for (auto ch : s)
				push_back(ch);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;
				reserve(s._capacity);
				for (auto ch : s)
					push_back(ch);
			}
			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];
				if (_str)
				{
					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
	};


	tutu::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		tutu::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		return str;
	}
}
  • 首先对于左值引用的使用场景:

左值引用做参数和做返回值都可以减少不必要的拷贝,提高效率。

例如:

void func1(tutu::string s)
{}
void func2(const tutu::string& s)
{}
int main()
{
	tutu::string s1("hello world");
	// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
	func1(s1);
	func2(s1);
}

结果如下:


但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:将整型转换为string类型的函数:string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造):


这里可以看到传值返回时需要拷贝构造两次,但是编译器一般会给出优化,只拷贝一次:


但是不管怎样,传值返回都至少需要拷贝一次,所以这时候右值引用就可以帮助我们啦

  • 对于右值引用的使用场景:

因为右值一般是临时对象,都是将亡值,所有我们可以在bit::string中增加移动构造移动赋值,对右值对象使用,将参数右值的资源窃取过来,占位已有,那么就不用进行深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。这就像那些因疾病或者意外即将去世的人,如果签署了器官捐赠,就可以将他们的资源交换给别人。

// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);//直接交换s的资源,因为s是临时对象,很快就会被销毁
		}

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

这样对于将整型转换为string类型的函数:string to_string(int value)函数中传值返回构造的临时对象是右值,很快就会被销毁,所以将它拷贝给str时我们就可以通过移动构造直接交换临时对象的内容:


这里可以看到传值返回时需要构造两次,但是编译器一般会给出优化,将临时对象的拷贝省去,只构造一次:


我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。同样对于移动赋值,如果被赋值对象是右值,我们就可以走移动赋值,交换右值的资源,不用再进行深拷贝。


5.结语

  区分右值还是左值的关键在于是否可以取地址,对于左值的引用是左值引用,右值的引用是右值引用,它们都是给对象取别名。const左值引用可以引用右值,右值引用可以引用move后的左值;右值一般是临时对象,是将亡值,所以我们就可以在类中专门写一个供右值对象使用的拷贝构造和赋值重载函数,来置换它们的资源,从而减少不必要的深拷贝,我们将这两个函数称为移动构造和移动赋值。以上就是今天所有的内容啦~ 完结撒花 ~🥳🎉🎉

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

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

相关文章

webCppCluster

1.通讯协议、接口协议、数据传输格式之间的区别&#xff1f; 通讯协议 在TCP/IP四层模型中&#xff0c;四层分别是&#xff1a;应用层、传输层、网络层、网络接口层。 应用层通讯协议的代表&#xff1a;HTTP HTTPS 主要规定传输消息的具体内容、什么格式传输、是请求还是相应…

ueditorplus百度编辑器集成秀米及135编辑器

备用地址&#xff1a;ueditorplus百度编辑器集成秀米及135编辑器 下载拉取&#xff1a;ueditorplus: UEditorPlus 是基于 UEditor 二次开发的富文本编辑器&#xff0c;让 UEditor 焕然一新,已集成秀米、135编辑器&#xff0c;会不定时更新&#xff01;&#xff01;&#xff01…

MobaXterm 终端工具使用

文章目录 MobaXterm 相关介绍下载安装 MobaXterm添加 SSH 连接 MobaXterm 相关介绍 MobaXterm 是一款功能强大的终端仿真器和远程计算工具&#xff0c;专为 Windows 用户设计&#xff0c;提供了一站式解决方案&#xff0c;以便在本地和远程计算环境中工作。它结合了终端仿真、S…

C++设计模式——Chain of Responsibility职责链模式

一&#xff0c;职责链模式的定义 职责链模式&#xff0c;又被称为责任链模式&#xff0c;是一种行为型设计模式&#xff0c;它让多个对象依次处理收到的请求&#xff0c;直到处理完成为止。 职责链模式需要使用多个对象&#xff0c;其中的每个对象要么处理请求&#xff0c;要…

『功能项目』坐骑UI搭建及脚本控制显/隐【19】

本章项目成果展示 我们打开上一篇18怪物消亡掉落宝箱的项目&#xff0c; 本章要做的事情是搭建一个坐骑UI界面&#xff0c;并通过键盘B键/右侧坐骑按钮控制坐骑UI界面的显示与隐藏 在背包Bag上创建一个父物体&#xff0c; 命名为Middle 修改Bag的尺寸 将下面资源图片放进Art文…

开源|FormCreate低代码表单在弹窗中渲染表单时表单的值没有正常清空解决方法

如何在弹窗中通过低代码表单 FormCreate 渲染表单&#xff0c;包括表单的配置、表单验证、以及表单提交的处理。 源码地址: Github | Gitee <template><div><!-- 触发弹窗的按钮 --><el-button type"primary" click"showDialog true&quo…

国家商用密码算法——SM1、SM2、SM3

1、SM1 SM1 是中国国家密码管理局&#xff08;SCA&#xff09;发布的国密算法之一&#xff0c;属于对称加密算法&#xff0c;其分组长度、秘钥长度都是128bit。 【注】对称加密算法是一种使用相同密钥进行数据加密和解密的加密方式。在这种算法中&#xff0c;发送方和接收方共…

将本地的 IntelliJ IDEA 项目导入到 GitLab 上——超详细图文教程

要将本地的 IntelliJ IDEA 项目导入到 GitLab 上&#xff0c;可以按照以下详细步骤进行操作&#xff1a; 1. 在 GitLab 上创建一个新的仓库 打开 GitLab 或公司内部的 GitLab 服务器。 登录你的 GitLab 账号。 点击右上角的 号按钮&#xff0c;然后选择 “New Project”。 …

清华MEM作业-利用管理运筹学的分析工具slover求解最优解的实现 及 通过使用文件或者套节字来识别进程的fuser命令

一、清华MEM作业-利用管理运筹学的分析工具slover求解最优解的实现 最近又接触了一些线性求解的问题&#xff0c;以前主要都是在高中数学里接触到&#xff0c;都是使用笔算&#xff0c;最后通过一些函数式得出最小或者最大值&#xff0c;最近的研究生学业上接触到了一个Excel s…

C++入门基础知识50——【关于C++数字】之C++ 数学运算

成长路上不孤单&#x1f60a;【14后&#xff0c;C爱好者&#xff0c;持续分享所学&#xff0c;如有需要欢迎收藏转发&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#xff01;&#xff01;&#xff01;&#xff01;&#xff…

C++string类相关OJ练习(2)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 Cstring类相关OJ练习(2) 收录于专栏【C语法基础】 本专栏旨在分享学习C的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1.反转字符串 …

录屏软件电脑,精选5款录屏神器推荐

嘿&#xff0c;朋友们&#xff01;想象一下&#xff0c;你正在与好友分享你最新的游戏成就&#xff0c;或是与同事展示你的最新项目进展&#xff0c;但却发现文字描述无法完美呈现你的精彩瞬间。别担心&#xff0c;在这个数字化的时代&#xff0c;我们有着无数种方式记录和分享…

大型集团行业ITSM案例分析报告

一、项目背景 随着信息化建设的不断推进&#xff0c;大型集团的信息系统规模迅速扩大&#xff0c;业务系统对IT的依赖程度逐渐加深&#xff0c;IT网络应用系统的复杂度也随之增加。然而&#xff0c;相对滞后的运维服务体系却未能同步跟进&#xff0c;运维要求不断提高的同时&a…

SparkRA带你读论文 | 如何训练数据高效的 LLMs

简介 How to Train Data-Efficient LLMs 论文作者&#xff1a; Noveen Sachdeva, Benjamin Coleman, Wang-Cheng Kang, Jianmo Ni, Lichan Hong Ed H. Chi, James Caverlee, Julian McAuley, Derek Zhiyuan Cheng 论文链接&#xff1a; https://arxiv.org/pdf/2402.09668.pd…

Java CRM客户关系管理系统源码:基于Spring Cloud Alibaba与Spring Boot,专为成长型企业设计

项目名称&#xff1a;CRM客户关系管理系统 功能模块及描述&#xff1a; 一、待办事项 今日需联系客户&#xff1a;显示当日需跟进的客户列表&#xff0c;支持查询和筛选。分配给我的线索&#xff1a;管理分配给用户的线索&#xff0c;包括线索列表和查询功能。分配给我的客户…

Hive数据库与表操作全指南

目录 Hive数据库操作详解 创建数据库 1&#xff09;语法 2&#xff09;案例 查询数据库 1&#xff09;展示所有数据库 &#xff08;1&#xff09;语法 &#xff08;2&#xff09;案例 2&#xff09;查看数据库信息 &#xff08;1&#xff09;语法 &#xff08;2&#…

【免费分享】嵌入式Linux开发板【入门+项目,应用+底层】资料包一网打尽,附教程/视频/源码...

想要深入学习嵌入式Linux开发吗&#xff1f;现在机会来了&#xff01;我们为初学者们准备了一份全面的资料包&#xff0c;包括原理图、教程、课件、视频、项目、源码等&#xff0c;所有资料全部免费领取&#xff0c;课程视频可试看&#xff08;购买后看完整版&#xff09;&…

U盘提示需要格式化才能使用怎么办?教你轻松应对

U盘作为一种便捷的数据存储设备&#xff0c;广泛应用于日常工作和生活中。然而&#xff0c;有时我们会遇到U盘插入电脑后提示需要格式化才能使用的情况&#xff0c;这让人倍感焦虑&#xff0c;因为格式化往往意味着数据丢失。不过&#xff0c;在采取极端措施之前&#xff0c;我…

如何验证mos管好坏

用万用表的二极管档位测试&#xff0c;只有D&#xff08;&#xff09;S&#xff08;-&#xff09;之间电压低于0.7v&#xff0c;其他任意两脚之间电压都是大于1.5V。这是正常的。

不限专业和工作经验,这个含金量巨高的IT证书,90%的大学生都不知道!

软考现在正在报名阶段&#xff0c;大学生们千万不要错过&#xff01;这个IT证书的含金量巨高&#xff0c;对你的大学生涯乃至毕业后的职业规划都有帮助&#xff01; 下面就来为大家详细讲解一番&#xff0c;速速码住&#xff01; 1、软考报名条件 软考报名没有学历、资历、年龄…