左值引用、右值引用及移动语义

个人主页:Lei宝啊 

愿所有美好如期而遇


左值

概念

可以取到地址的值就是左值,并且一般情况下可以修改(const类型左值不可修改)。

左值举例:

//左值
int a = 0;
const int b = 1;
int* p = &a;

右值

概念

不能取到地址的值就是右值,并且右值不能被修改。(字面常量,表达式返回值,函数返回值(非左值引用)),我们之前使用的就是左值引用。

右值举例:

int func()
{
	return 1 + 1;
}

//右值
10;
a + b;
func();

左值引用

概念

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

	int a = 0;
	const int b = 1;
	int* p = &a;

    //左值引用
	int& c = a;
	const int& d = b;
	int*& e = p;

右值引用

概念

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

	10;
	a + b;
	func();

	//右值引用
	int&& e = 10;
	int&& f = a + b;
	int&& g = func();

互相引用 

左值引用右值需要加const,右值引用左值需要将左值先进行move。

	//左值引用右值
	const int& f = 10;
	//右值引用左值
	int&& g = move(a);

应用与解释

简单来说就是为什么需要右值引用,我们先来看例子:

也就是说,临时对象的产生其实是多余的,所以在没有右值引用时,编译器给出的优化方案就是优化掉临时对象,直接使func中vv拷贝构造main中vv,可即便如此,还是有一次无谓的拷贝,就是func中的vv,他仍要销毁,资源还是浪费了。

接下来使用我们自己实现的string,来对有无右值引用做对比。

#include <iostream>
#include <string>
#include <string.h>
#include <cassert>
#include <algorithm>

using namespace std;

namespace own
{
	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)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			_str = new char[s._capacity+1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

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

			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
	};

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

		own::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;
	}
}

这里博主使用g++编译器,并且关闭了部分优化,在Visual  Studio 2022中,优化非常大,我们看不出他的过程,无法更好地进行对比,所以这里在Linux下进行演示,并使用-fno-elide-constructors关闭g++的编译优化。

举例一:仅有左值引用

一:
int main()
{
    own::string ret = own::to_string(1234);
    return 0;
}

这也就是我们上面得出的结果。

二:
int main()
{
    own::string ret;
    ret = own::to_string(-1234);

    return 0;
}

这种编译器是不会优化的,所以一般来说将他们写在一行上。

举例二:加入右值引用

		// 移动构造 -- 右值(将亡值)
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}

我们这里为什么要加入右值引用呢?首先,右值也叫做将亡值,也就是即将销毁的值。我们希望能够将这个将亡值利用起来,拿走他的资源。

我们可以想象一下,可以拿走左值的资源吗?左值引用左值,左值可能仍要被使用,如果这么被swap拿走资源是不可以的,但是将亡值,也就是对右值这样做却是我们希望看到的。 

一:
int main()
{
    own::string ret = own::to_string(1234);
    return 0;
}

这和我们上面的结果是一致的。

二:
int main()
{
    own::string ret;
    ret = own::to_string(-1234);

    return 0;
}

加入移动赋值拷贝

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

			return *this;
		}
三:
int main()
{
    own::string ret;
    ret = own::to_string(-1234);

    return 0;
}

看了这么久也许你有一个疑惑,移动构造和移动赋值,都要交换右值的资源,但是右值不是不能被修改吗?

那么这里我们说:右值引用 引用右值后的属性为左值。

也就是说,一个右值,被右值引用后,那个右值引用的属性将变成左值,于是swap中的s属性是左值,也就可以传过去了。

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lei宝啊

觉得博主写的有用就鼓励一下吧

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

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

打赏作者

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

抵扣说明:

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

余额充值