C++11_右值引用与移动语义

       

目录

1、左值的定义

1.1 左值引用

2、右值的定义

2.1 右值引用

3、右值与左值的使用区别 

4、右值引用的意义

4.1 左值引用的短板 

5、移动语义

5.1 移动构造

5.2 移动赋值 

6、万能引用

6.1 右值的别名-左值化

6.2 完美转发 


前言:

        在C++11之前就有了引用这个概念,只是全部都是作用于左值的,即给左值取别名。而到了C++11后,引入了右值引用的概念,即给右值取别名,无论是左值引用还是右值引用实际上都是给对象取别名,只不过定义左值与右值的概念不同。

1、左值的定义

         第一眼看到左值可能会以为出现在‘=’左边的值都称为左值,的确出现在‘=’左边的值都可以称为左值,但是左值也可以出现在‘=’的右边,示例如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

int main()
{
	int a = 1;
	int c = a;//a是一个左值,但是他可以出现在‘=’右边
    //c也是左值
	return 0;
}

        通常我们会认为可以被修改的值叫做左值,而在此之前我们会把一些不能被修改的值叫做常量(不认为其是左值),或者说其具有常属性(比如被const修饰了的左值),那么被const修饰过的左值确实不能被修改了,但是他依然是左值,原因在于凡是可以被赋值或被取地址的都叫做左值。示例代码如下: 

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

int main()
{
	// 以下的p、a、c、*p都是左值

	int* p = new int;//可以对p进行赋值或者取地址
	int a = 1;//可以对a进行赋值或者取地址
	const int c = 2;//虽然不能对c进行赋值,但是可以取其地址

	return 0;
}

1.1 左值引用

        左值引用顾名思义就是给左值进行取别名,示例如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

int main()
{
	// 以下的p、a、c、*p都是左值

	int* p = new int;//可以对p进行赋值或者取地址
	int a = 1;//可以对a进行赋值或者取地址
	const int c = 2;//虽然不能对c进行赋值,但是可以取其地址

	// 以下几个是对上面左值的左值引用(侧面可以证明上述是左值)

	int*& rp = p;//rp是一个指针的别名,他指向p指向的内容
	int& ra = a;
	const int& rc = c;
	int& rvp = *p;//rvp是一块类型为int类型空间的别名

	return 0;
}

2、右值的定义

        左值可以出现在‘=’号的左右,但是右值只能出现在‘=’号的右边,因此意味着右值是不允许被直接修改,比如字面常量、函数的值返回、表达式的结果都是右值,并且不能够直接对他们进行修改,也不能够直接取右值的地址

        示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

double func(double x, double y)
{
	return x > y ? y : x;
}

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	12;
	x + y;
	func(x, y);
	
	//	以下三行代码会报错,原因就是不能够直接对右值进行修改
	12 = 1;
	x + y = 1;
	func(x, y) = 1;
	//  并且不能够直接取他们的地址
	&12;
	&(x + y);
	&func(x, y);

	return 0;
}

        函数值返回和表达式结果为右值的原因如下图所示:

2.1 右值引用

        左值引用的写法一般是在类型的右边加上一个取地址符号‘&’,而右值引用的写法是在类型的右边加上两个取地址符号‘&&’,并且对右值进行引用后,可以取到别名的地址,并且可以对别名进行修改

        示例代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

double func(double x, double y)
{
	return x > y ? y : x;
}

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	12;
	x + y;
	func(x, y);

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

	//可以对别名进行修改和取地址
	cout << ++rr1 << endl;
	cout << &rr1 << endl;

	cout << ++rr2 << endl;
	cout << &rr2 << endl;

	cout << ++rr3 << endl;
	cout << &rr3 << endl;

	return 0;
}

        运行结果:

3、右值与左值的使用区别 

        1、左值引用符号是一个取地址符‘&’,右值引用符号是两个取地址符‘&&’。

        2、左值可以对其进行赋值和取地址,右值不可以对其进行赋值和取地址。

        3、左值可以出现在‘=’号的左右,但是右值只能出现在‘=’号的右边。

        4、左值引用不能引用右值,右值引用不能引用左值。

        5、在第4点的基础上,左值引用加上const后可以引用右值,右值引用可以引用被函数move调用后的左值。

        上述第五点的测试代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

int main()
{
	const int& a = 1;//左值引用右值

	int b = 12;
	int&& rb = move(b);//右值可以引用被move后的左值
	rb = 1212;//并且可以通过rb更改b的值
	cout << b << endl;
	cout << rb << endl;

	return 0;
}

        运行结果:

4、右值引用的意义

         既然左值引用被const修饰后可以引用右值,那么为什么还要引出右值引用这个概念呢?因为左值引用被const修饰后虽然可以引用右值,但是编译器区分不了被引用的对象是左值还是右值,最主要的是被修饰的右值不能够被修改了。体现这一细节的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void func(const int& a)//期望左值进该函数
{
	cout << "void func(int& a)" << endl;
}

void func(int&& a)//期望右值进该函数
{
	cout << "void func(int&& a)" << endl;
}

int main()
{
	int a = 12;
	func(a);
	func(12);
	return 0;
}

        运行结果:


        若没有右值引用,则上述的代码中,两次调用func函数都只能进入void func(const int& a),那么我们所期望的调用左值和右值所呈现不同内容的目的就实现不了了。

4.1 左值引用的短板 

        左值引用常常被用于函数的形参函数的传引用返回,因为在这种场景下左值引用可以减少一些不必要的拷贝工作,可以节省空间和提供效率,但是传引用返回有局限性,即引用的对象的生命周期必须出了该函数栈帧还存在。因此当我们想返回一个局部对象给到外部时,就不能使用传引用返回了,因为涉及到权限越界访问的问题。

        所以当返回局部对象时,会经过两重的拷贝构造,具体示意图如下:

        从上图可以看到,临时对象的作用只是对st1对象进行深拷贝,并且拷贝任务完成后会销毁,如果能够把临时对象的内容充分利用到极致,就可以大大的提供效率了,而右值引用的作用在这里就体现出来了,对临时对象进行右值引用,则就可以对临时对象的内容进行修改了,通常会利用右值引用将临时对象里的数据和st1的数据进行交换,这样一来就省去了深拷贝这个步骤,并且把这一步叫做移动构造

5、移动语义

        移动语义即移动构造移动赋值,他的作用主要是通过资源的交换从而避免一些繁琐的工作,达到提高效率的目的,实现移动语义的前提是右值引用。

5.1 移动构造

        移动构造的思想是将右值中的资源进行转移,并用来初始化另一个对象,并且把未初始化对象的内容给到该右值,形象的来说就是窃取右值的资源来构造新的对象,这时候就无需进行深拷贝了。

        移动构造其实是拷贝构造的一个函数重载,只不过拷贝构造的形参类型一般是const+左值引用,因为拷贝构造不希望修改被拷贝的对象。而移动构造的形参类型是没有加const的右值引用,可以通过其别名修改右值。具体示意图如下:


        因此有了移动构造后,当右值要调用拷贝构造就会进入移动构造,而不是拷贝构造,比如上述例子:

5.2 移动赋值 

        移动赋值的思想和移动构造是一样的,也是通过资源的交换实现的。

        移动赋值与拷贝赋值在写法上的区别: 

        移动赋值的实现代码:

// 移动赋值
string& operator=(string&& s)
{
	swap(s);
	return *this;
}

        移动赋值的调用过程:

6、万能引用

         万能引用又叫引用折叠,他可以引用左值和右值,但是他的写法和右值引用是一样的,只不过他作为一个模板参数,要作用在函数模板下。

        万能引用写法如下:

template<typename T>
void Universal_citation(T&& t)
{
	//此处的形参T&&表示的是万能引用,t可以接收左值和右值
    //t接收右值时是右值引用,接收左值时被折叠成了左值引用
}

6.1 右值的别名-左值化

         从上文可以得到,右值的别名是可以更改的,比如以上的移动构造移动赋值,他们的形参都是一个右值引用,但是右值本身是不可以被更改的,当右值通过右值引用后,就可以通过别名来更改右值的内容,所以才能够在移动构造移动赋值的函数中进行资源的交换。形象的来说,右值的别名可以看作是一个左值

        比如以下代码就可以证明右值别名的左值化:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void func(int& x) { cout << "左值引用" << endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用" << endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void Universal_citation(T&& t)//此处的形参T&&表示的是万能引用,可以接收左值和右值
{
	func(t);
}
int main()
{
	Universal_citation(10);
	int a;
	Universal_citation(a);//左值
	Universal_citation(move(a)); // 右值
	const int b = 8;
	Universal_citation(b);//const 左值
	Universal_citation(move(b)); // const 右值
	return 0;
}

        运行结果:

        从结果可以发现,调用函数Universal_citation的实参是右值,但是经过右值引用后,调用func函数的实参变成了左值,因此打印出来的都是”左值引用“。

6.2 完美转发 

        完美转发的写法如下:

std::forward<T>

         他主要是用于参数在传递的过程中可以保留原来的属性,比如其可以让右值通过右值引用后,别名依然保留右值属性。注意:他的生效范围和move一样,只在当前行有效,比如:move(a),在当前行可以使a变成右值,但是到了下一行a还是左值。

        将上述代码进行完美转发: 

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

void func(int& x) { cout << "左值引用" << endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用" << endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }

template<typename T>
void Universal_citation(T&& t)//此处的形参T&&表示的是万能引用,可以接收左值和右值
{
	func(std::forward<T>(t));
}
int main()
{
	Universal_citation(10);
	int a;
	Universal_citation(a);//左值
	Universal_citation(move(a)); // 右值
	const int b = 8;
	Universal_citation(b);//const 左值
	Universal_citation(move(b)); // const 右值
	return 0;
}

        运行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

143.和弦是什么?和声是什么?三和弦

内容参考于&#xff1a; 三分钟音乐社 上一个内容&#xff1a;142.音程的构唱练习 和弦的定义&#xff1a; 一个音可以把它称为单音 两个音可以把它称为音程 更多的音&#xff0c;通俗的定义上&#xff0c;三个音或者三个以上的音构成的集体就可以叫做和弦&#xff0c;这些音…

如何将虚拟机设置成固定IP

问题描述&#xff1a; 在VMware虚拟机上部署的项目ip地址和数据库ip地址发生变动&#xff0c;导致mysql,nginx,redis等无法访问&#xff0c;要改配置又特别麻烦&#xff0c;而且下次可能还会变动。 解决方法&#xff1a; 将虚拟机ip地址配置成固定ip 关闭虚拟机&#xff0c;找…

【SpringMVC】快速体验 SpringMVC接收数据 第一期

文章目录 一、SpringMVC 介绍1.1 主要作用1.2 核心组件和调用流程理解 二、快速体验三、SpringMVC接收数据3.1 访问路径设置3.1.1 精准路径匹配3.1.2 模糊路径匹配3.1.3 类和方法级别区别3.1.4 附带请求方式限制3.1.5 进阶注解 与 常见配置问题 3.2 接收参数&#xff08;重点&a…

Vulnhub内网渗透Jangow01靶场通关

详细请见个人博客 靶场下载地址。 下载下来后是 .vmdk 格式&#xff0c;vm直接导入。 M1请使用UTM进行搭建&#xff0c;教程见此。该靶场可能出现网络问题&#xff0c;解决方案见此 信息搜集 arp-scan -l # 主机发现ip为 192.168.168.15 nmap -sV -A -p- 192.168.168.15 # 端…

python的虚拟环境

python的虚拟环境可以为项目创建一个独立的环境&#xff0c;能够解决使用不同版本依赖给项目带来冲突的麻烦。创建虚拟环境的方式有很多种&#xff0c;pipenv会自动帮你管理虚拟环境和依赖文件&#xff0c;并且提供了一系列命令和选项来帮忙你实现各种依赖和环境管理相关的操作…

【动态规划.3】[IOI1994]数字三角形 Number Triangles

题目 https://www.luogu.com.cn/problem/P1216 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 7→3→8→7→5 的路径产生了最大权值。 分析 这是一个动态规划…

VMware虚拟机

1、虚拟机介绍 虚拟机&#xff08;Virtual Machine&#xff09;是一种软件&#xff0c;可以用来模拟具有完整硬件功能的完整的计算机系统的软件&#xff0c;并且可以和主机环境隔离开&#xff0c;互不影响。也就是&#xff0c;在实体计算机中能够完成的工作都可以通过虚拟机实…

哨兵系列数据下载(哨兵2号Sentinel-2下载)

目录 一、介绍 二、哨兵二号介绍 三、数据下载 1、注册账号 2、数据下载 3、相关问题 四、数据预处理 1、大气校正 2、重采样 五、其他问题 一、介绍 哨兵&#xff0d;1卫星是全天时、全天候雷达成像任务&#xff0c;用于陆地和海洋观测&#xff0c;首颗哨兵&#xf…

Python PyQt5 多Tab demo

参考&#xff1a; https://cloud.tencent.com/developer/news/388937 importsysfromPyQt5.QtWidgetsimportQVBoxLayout,QWidget,QFormLayout,QHBoxLayout,QLineEdit,QRadioButton,QCheckBox,QLabel,QGroupBox,QApplication,QTabWidgetclassTabDemo(QTabWidget):def__init__(se…

并查集(蓝桥杯 C++ 题目 代码 注解)

目录 介绍&#xff1a; 模板&#xff1a; 题目一&#xff08;合根植物&#xff09;&#xff1a; 代码&#xff1a; 题目二&#xff08;蓝桥幼儿园&#xff09;&#xff1a; 代码&#xff1a; 题目三&#xff08;小猪存钱罐&#xff09;&#xff1a; 代码&#xff1a; …

OpenCASCADE+Qt创建建模平台

1、建模平台效果 2、三维控件OCCWidget 将V3d_View视图与控件句柄绑定即可实现3d视图嵌入Qt中&#xff0c;为了方便也可以基于QOpenGLWidget控件进行封装&#xff0c;方便嵌入各种窗体使用并自由缩放。 #ifndef OCCTWIDGET_H #define OCCTWIDGET_H#include <QWidget> #i…

云轴科技ZStack荣获证券基金行业信息技术应用创新联盟年度优秀成员奖

近日&#xff0c;由中国证监会科技监管司、上海市经济和信息化委员会及上交所理事会科技发展委员会指导&#xff0c;证券基金行业信息技术应用创新联盟&#xff08;简称信创联盟&#xff09;主办的2023年年度工作会议在上海成功举办。会议汇聚了来自监管机构、政府机构、行业侧…

继深圳后,重庆与鸿蒙展开原生应用开发合作

截至2023年底&#xff0c;开源鸿蒙开源社区已有250多家生态伙伴加入&#xff0c;开源鸿蒙项目捐赠人达35家&#xff0c;通过开源鸿蒙兼容性测评的伙伴达173个&#xff0c;累计落地230余款商用设备&#xff0c;涵盖金融、教育、智能家居、交通、数字政府、工业、医疗等各领域。 …

this.$set,更新vue视图

this.$set(this.searchForm, age, 30) // 对象 this.$set(this.searchForm1, 0, { name: 汪汪, age: 11, content: 擅长口算 })// 数组

Android使用WebView打开外部网页链接

发布Android应用&#xff0c;除了用原生开发外&#xff0c;更多是采用内嵌H5网页的方式来做&#xff0c;便于更新以及多平台使用。 一、第一种方式是直接通过WebView打开外部H5链接。 新建Android工程 直接创建一个工程&#xff0c;点击运行就可以了&#xff0c;打开是个空页…

STM32(14)USART

USART:一种片上外设&#xff0c;用来实现串口通信&#xff0c;就是stm32内部的串口 USART简介 串并转换电路 串行通信和并行通信 串行&#xff1a;一根数据线&#xff0c;逐个比特位发送 为什么要串并转换 移位寄存器 USART的基本模型 通过查询SR&#xff08;状态寄存器&…

加速大模型落地:火山引擎向量数据库的实践应用

近两年随着大模型技术的快速发展&#xff0c;图片、视频、自然语言等多模态、非结构化数据的查找需求变大&#xff0c;非结构化数据的量级也远大于结构化数据&#xff0c;传统数据库已经无法满足如此多样化数据的处理需求。向量数据库以其海量的数据存储规模、高效的计算查询能…

并发安全问题(超卖问题)

一&#xff0c;问题解析 超买问题就是&#xff0c;原本库存中有200件库存&#xff0c;结果由于并发问题售出了300件这就是炒卖问题对于买东西无非就是 查询商品&#xff0c;判断库存是否充足&#xff0c;如果充足则下单成功。 这里采用的是先查询&#xff0c;再判断&#xff0c…

复杂业务场景下,如何优雅的使用设计模式来优化代码?

1、引言 本文以一个实际案例来介绍在解决业务需求的路上&#xff0c;如何通过常用的设计模式来逐级优化我们的代码&#xff0c;以把我们所了解的到设计模式真实的应用于实战。 2、背景 假定我们现在有一个订单流程管理系统&#xff0c;这个系统对于用户发起的一笔订单&#…

MyBatis-Plus如何娴熟运用乐观锁

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MyBatis-Plus如何娴熟运用乐观锁 前言乐观锁的基本概念基本概念和原理&#xff1a;为何乐观锁是解决并发问题的有效手段&#xff1a; MyBatis-Plus中乐观锁的支持1. Version 注解&#xff1a;2. 配置乐…
最新文章