C++11重大新增特性:左值引用 & 右值引用 & 移动构造 & 移动赋值

一、右值引用和左值引用概念和区别

 C++11为了支持移动操作(移动构造和移动赋值),新标准引入了新的引用类型 —— 右值引用。我们将C++11之前的引用都成为左值引用。但无论是左值引用还是右值引用,本质上都是给对象取别名!

1.1 左值 & 左值引用

 左值是一个表达式(如变量、解引用后的指针),表示的是一个对象的身份。我们可以对左值进行赋值操作、取地址。左值可以出现在等号的两边。

 评判一个表达式是否为左值的最根本标志就是:是否可以取地址。所以对于一个const修饰的变量,由于该变量可以取地址,所以也是一个典型的左值。左值引用即是左值的别名。

int main()
{
   
   
	//a、b、p、*p都是左值
	int* p = new int(0);
	int a = 10;
	const int b = 12;

	//rp、ra、rb、rval都是左值引用
	int*& rp = p;
	int& ra = a;
	const int& rb = b;
	int& rval = *p;
	return 0;
}

1.2 右值 & 右值引用

 右值也是一个表达式,和左值不同的是:右值只能出现在等号的右边(即不能被赋值),不能取地址(最根本原因),通常是字面常量、表达式返回值,函数返回值。右值引用就是对右值的引用,通过&&来获取右值引用。

右值不能取地址。但对右值取别名后,会导致右值被存储到特定的区域,并且可以取到该区域的指针。比如:字面常量10是一个右值,不能取地址。如果10被ra引用后,我们可以对ra取地址,并且可以通过修改ra进而修改右值。如果不想该右值被修改,我们可以通过const进行修饰!!

int Add(const int x, const int y)
{
   
   
	return x + y;
}

int main()
{
   
   
	//10、10 + 20、Add函数返回值都是右值,无法取地址
	//ra、rb、rc都是右值引用
	int&& ra = 10;
	int&& rb = 10 + 20;
	int&& rc = Add(1, 2);
	ra = 20;
	return 0;
}
  • 需要注意的是:上述ra、rb、rc虽然是右值引用,但ra、rb、rc本身还是一个变量,并且可以取地址,是一个左值!!

二、左值引用和右值引用对比

2.1 左值引用

  1. 普通的左值引用只能引用左值,不能引用右值。
  2. const修饰的左值引用,不仅可以引用左值,还可以引用右值!!

【示例】:

int main()
{
   
   
	int a = 10;

	int& ra = a;
	//int& rb = 10;//error,普通左值引用不能引用右值
	const int& rc = a;
	const int& rd = 10;
	return 0;
}

2.1 右值引用

  1. 右值引用可以引用右值,但不能引用左值。
  2. 我们可以通过move函数,让右值引用引用左值!!move函数调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。需要注意的是,move函数的返回值是一个右值,但move函数本身不会修改左值属性!

【示例】:

int main()
{
   
   
	int a = 10;
	const int&& rb = 10;

	//int&& ra = a;//error, 右值引用无法引用左值
	//error,move本身不会修改左值属性
	//move(a);
	//int&& ra = a;

	int&& ra = move(a);//move后的返回值是一个右值
	return 0;
}

三、右值和右值引用诞生的意义

 在C++11标志之前,如果在vector、list等容器保存的是一块空间的指针。此时如果调用拷贝构造函数和拷贝赋值函数,编译器会进行一个深拷贝构造出新对象,将就对象释放。
 在很多情况下拷贝对象时无法避免的。

但如果返回的是一个临时对象会发生什么呢?

【示例】:
在这里插入图片描述

  • 我们发现如果用一个临时对象拷贝构造一个变量s时,编译器会先拷贝构造出一个临时对象,在用该临时对象出拷贝构造变量s
  • 但该过程中存在一个问题:临时对象深拷贝创建后仅仅使用一次便立即销毁、原对象ret是一个临时对象马上就要出作用域销毁,此时依旧对ret进行拷贝构造。
  • 上述情况在实际过程中会在大量场景中频繁出现,并且意义不大。这也意味着大量的无意义的深拷贝产生,将导致性能的下降。我们是否可以不进行拷贝,直接将原始数据转移到新对象中呢?(该操作的前提是原始数据马上就要被销毁)
  • 为了解决上述情况,C++11引入了移动构造函数和移动拷贝函数。移动构造函数和移动拷贝函数可以将一个待销毁的变量数据(该变量通常被编译器识别为右值)直接转移到新对象。而右值和右值引则是为实现这些函数运营而生的!!

四、移动构造 & 移动赋值

 在C++中,右值分为两种:内置定义类型右值为纯右值;自定义类型右值为将亡值!!对于纯右值,移动构造函数、移动赋值函数没有太大价值,行为和拷贝构造函数、移动构造函数类型。(上述临时对象ret虽然可以取地址是一个左值,但编译器会特殊处理将其识别为右值,即将亡值)
 只有当自定义类型中存在资源的深拷贝时,此时才能移动构造和移动赋值的价值。(直接转移资源,而非深拷贝!!)

4.1 移动构造函数

 类似于拷贝构造函数,移动构造函数的第一个参数是该类类型的引用,不同的是引用参数是一个右值。移动构造函数的本质是直接将右值对象的资源窃取过来,占为己有,此时不在进行深拷贝。所以该构造称为移动构造,用别人的资源来构造自己。

【示例:移动构造和拷贝构造函数实现和对比】:

namespace mystring
{
   
   
	class string
	{
   
   
	public:
		string(const char* str = "")//默认构造函数
			:_size(strlen(str))
			, _capacity(_size)
		{
   
   
			cout << "string(const char* str = "") ----- 构造函数" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		//拷贝构造函数
		string(const string& s)
		{
   
   
			cout << "string(const string& s) ---- 拷贝构造函数 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		//移动构造函数
		string(string&& s)
		{
   
   
			cout << "string(string&& s) ---- 移动构造函数  移动语义" << endl;
			swap(s);
		}

		void swap(string& s)
		{
   
   
			std::swap(_str, s._str);
			std::swap(_size, s._size
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小白debug~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值