[C++] C++11详解 (二)右值引用与移动语义

标题:[C++11] 右值引用与移动语义

@水墨不写bug



目录

一、C++11的新增语法:右值引用

1.左值与左值引用

a.简介

b.在语法上:

c.常见的应用场景:

2.右值与右值引用

a.简介

b.语法

c.常见应用场景

右值引用

3、左值引用与右值引用总结

二、移动语义

1.移动构造 

 2.其他移动语义:移动赋值


正文开始:

一、C++11的新增语法:右值引用

        引用是C++中传统的语法,而C++11中新增了右值引用的语法,因此,为了区分,从现在开始,我们之前学的引用称为左值引用。无论是左值引用还是右值引用,都是给对象取别名。

1.左值与左值引用

a.简介

        左值是一个表示数据的表达式(如变量名或解引用的指针)。我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边。

        定义时const修饰符后的左值,仍然是左值,不能给他赋值,但是可以取它的地址左值引用就是给左值的引用,给左值取别名。


b.在语法上:

普通引用(左值引用)

  • 语法形式:类型& 引用名 = 初始值;
  • 示例:int a = 10; int& ref = a;
  • 特点:
    • 必须在声明时初始化。
    • 只能绑定到左值上。
    • 相当于变量的别名,不占用额外内存空间。
#include<iostream> 
using namespace std;
int main()
{
	//p,b,c都是左值
	int* p = new int[3] {0};
	int b = 3;
	const int c = 1;

	//rp,rb,rc是对左值的引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;


	return 0;
}

c.常见的应用场景:

普通引用(左值引用)

  • 作为函数调用时传入参数,避免拷贝,提高效率
  • 作为函数返回值,可以返回对象的引用(但需保证引用对象在函数返回后依然有效)。


2.右值与右值引用

a.简介

        右值也是一个表示数据的表达式。如:字面常量、表达式返回值,函数返回值(不能是左值引用返回)等等。

        右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

#include<iostream> 
using namespace std;
int main()
{
	double x = 1.0;
	int y = 5;
	
	//一下是常见的右值
	19;
	x + y;
	fmax(x, y);

	//一下是对右值的引用
	int&& r1 = 19;
	double&& r2 = x + y;
	double&& r3 = fmax(x,y);

	//右值无法出现在赋值符号的左边
	//编译报错:左操作数必须为可修改的左值
	10 = 1;
	x + y = 10;
	fmax(x, y) = 0;

	return 0;
}

b.语法

右值引用(也称为移动语义引用)

  • 语法形式:类型&& 引用名 = 初始值;
  • 示例:int&& rref = std::move(a);
  • 特点:
    • 必须在声明时初始化。
    • 可以绑定到临时对象(右值)上。
    • 允许修改绑定的对象(这就是说,在右值引用之后,引用本身退化为左值)。

c.常见应用场景

右值引用
  • 实现移动语义和完美转发,提高资源管理的效率。
  • 用于编写高效的 移动赋值运算符重载移动构造函数
  • 在模板编程和泛型编程中,右值引用允许函数模板接受右值作为参数,进而实现移动语义。

 此外,补充注意:

        需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。

        左值引用可以引用右值,但是前提是需要将左值引用类型声明为const;

        右值引用也可以引用move之后的左值。 


3、左值引用与右值引用总结

        左值的特点是可以取地址,可以出现在赋值符号左侧和右侧。

左值引用总结:
        1. 左值引用只能引用左值,不能引用右值。
        2. 但是const左值引用既可引用左值,也可引用右值。

        右值的特点是无法取地址,可以出现在赋值符号右侧,但是不能出现在赋值符号左侧。

右值引用总结:
        1. 右值引用只能右值,不能引用左值。
        2. 但是右值引用可以move以后的左值。

二、移动语义

        其实,你一定早已发现:右值代表的是将亡值,它的生命周期将要到达尽头。而左值就是我们创建的一般变量。

1.移动构造 

        在下面这几个场景中,你会发现使用拷贝构造会很憋屈,这也就是移动构造要解决的问题。 


        场景一: 在给map的insert传递参数时,我们需要传递一个pair类型的参数,但是为了传递参数而专门创建一个对象,这就很麻烦。在这样的情况下我们通常选择传递一个临时对象的方法来解决。但是,传临时对象,也会有需要面临的问题:需要把临时对象深拷贝到map中。

        为了避免深拷贝,我们可以把右值的数据交换到map中,这样就减少了拷贝。这就引出了移动语义。

        场景二:一个函数需要传递一个局部变量返回,由于局部变量出了作用域就会销毁,所以只能传值返回,那么这样一般就需要两次拷贝:局部变量拷贝给临时对象,临时对象拷贝给接受返回值对象,非常浪费资源。

        在没有实现移动构造时,str会被识别为右值,但是编译器没有选择,只能使用深拷贝传值返回。但是在实现了移动构造之后,编译器会采用更加适合的方式:调用移动构造。

        为了能看清楚移动构造内部的情况,我们使用自己提前写好的string;移动构造参数类型为右值引用,传入的参数是一个将亡值,在函数内部,构造*this,然后需要把参数将亡值与*this的数据交换即可,实现如下:

// 移动构造
string(string&& s)
    :_str(nullptr)
    ,_size(0)
    ,_capacity(0)
{
    swap(s);
}

 2.其他移动语义:移动赋值

         在解释清楚移动构造之后,移动赋值就十分清晰了:函数参数为右值引用;在函数内部实现*this与将亡值的交换:

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

移动语义的意义: 


小结:

        左值引用做参数和做返回值都可以提高效率;但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
        移动构造的出现是为了实现减少拷贝,把将亡值的数据移动给目的处的赋值对象,移动构造中没有开辟新空间,拷贝数据,所以效率提高了。


完~

未经作者同意禁止转载 

  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水墨不写bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值