C++11第二弹:左右值 | 左右值引用 | 移动构造 | 完美转发

2024-09-09 09:33:40 浏览数 (2)

左值和右值概述

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

什么是右值?什么是右值引用? 右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

右值引用:

代码语言:javascript复制
int main()
{
	int x = 1, y = 2;
	int&& ret1 = (x   y);
	string&& ret2 = string("111");
	string&& ret3 = to_string(123);

	return 0;
}

左值引用不能给右值引用取别名,但是const左值引用可以。右值引用不能给左值取别名,但是可以给move以后的左值取别名。

代码语言:javascript复制
int main()
{
	const string& ret1 = string("1111");
	const int& ret2 = 10;
	string s1("2222");
	string&& ret3 = move(s1);
}

左右值意义及使用场景

引用的意义是减少拷贝提高效率。

左值引用的使用场景: 做参数和做返回值都可以提高效率

代码语言:javascript复制
void func1(gwj::string s)
{}
void func2(const gwj::string& s)
{}
int main()
{
gwj::string s1("hello world");
// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1(s1);
func2(s1);
// string operator =(char ch) 传值返回存在深拷贝
// string& operator =(char ch) 传左值引用没有拷贝提高了效率
s1  = '!';
return 0;
}

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

移动构造

代码语言:javascript复制
// 移动构造
string(string && s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	cout << "string(string&& s) -- 移动语义" << endl;
	swap(s);
}
// 移动赋值
string& operator=(string && s)
{
	cout << "string& operator=(string&& s) -- 移动语义" << endl;
	swap(s);
	return *this;
}

左值进行深拷贝,右值进行移动构造

在使用移动构造后,不再需要创建一个临时变量,直接将资源进行转移,不需要执行拷贝构造,大大提高效率

代码语言:javascript复制
namespace gwj
{
	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;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动构造
		string(string && s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动语义" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string && s)
		{
			cout << "string& operator=(string&& s) -- 移动语义" << endl;
			swap(s);
			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] = '';
		}
		//string operator =(char ch)
		string& operator =(char ch)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的
	};

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

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

}


int main()
{
	gwj::string ret1 = gwj::to_string(1234);
	cout << ret1.c_str() << endl;

	return 0;
}		

完美转发

函数模板中的&& 叫做万能引用

代码语言:javascript复制
template<typename T>
void PerfectForward(T&& t)
{
}

这里由于模板的存在,就不是右值引用,叫做万能引用,传左值就是左值引用,传右值就是右值引用,传const的左值,就是const的左值引用,传const的右值,就是const的右值引用。

通过运行结果发现,右值引用的属性被退化了,如果使用move,那么全部变成了右值。 由于右值引用,引用后,右值引用本身属性变成左值,为了解决这一问题,C 推出了std::forward 完美转发在传参的过程中保留对象原生类型属性。 实参传左值,就推成左值引用;实参传右值,就推成右值引用。

代码语言:javascript复制
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// std::forward<T>(t)在传参的过程中保持了t的原生类型属性
template<typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));
}

int main()
{
	PerfectForward(10); // 右值
	
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值
	
	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

0 人点赞