c++右值引用详解!

前言

左值引用可以参考笔者的这篇文章---从c到c++——4:引用-CSDN博客   (ps:这篇文章里的引用单只左引用笔者当时水平不高(虽然现在也不高)起错了名字)

左值引用与右值引用的定义

c++中,无论是左值引用与右值引用,用途都是在给对象起别名

左值与右值的概念

左值和右值是c++中的一个概念,严格的来说,对于系统提供的 =操作符 来说(自己提供的重载函数不算),可以放在等号左边的或者能加const的称为左值,只能放在 = 右边的称为右值,

举个例子:变量,指针的解引用表达式都为左值

                常量,函数返回值,计算式都为右值

巧计:可以取地址的都为左值,不可取地址的都为右值

// 一个特殊的情况
const int& fun() {
	return 1 + 2;
}
int main() {
	const int& f = fun();      // 没有报错
	printf("%p\n", &fun());    // 一个指针
	printf("%d\n", fun());     // 3
	return 0;
}

此时 fun()的返回值可以取地址,证明是左值,他不能放在=左边是因为他的返回值是const,但这个函数的返回值的的确确是一个左值(或者说返回了一个被强转为左值的右值)

ps:这是一种没什么意义的写法,大伙就当看个乐子,知道有这么回事就行,实际没什么用

左值引用与右值引用定义时的语法

左值引用需要在变量类型之后,名字之前加 &

右值引用需要在变量类型之后,名字之前加 &&

move是一个库函数,可以把左值强制转为右值

左值引用左值,右值引用右值如下

int main() {
	int a = 0;			
	int& aa = a;	// 左值引用

	const int b = 0;
	const int& bb = b;	//const 左值引用

	int&& cc = 0;		// 右值引用

	const int&& dd = 0; // const右值引用

	const int& ee = 0;	//必须加const 左值引用右值

	int f = 0;
	int&& ff = move(f);	// 右值引用左值必须加move

	const int g = 0;
	const int&& gg = move(g); // 右值引用左值必须加move

	return 0;
}

左值引用右值必须加const

右值引用左值需要move(左值),如果左值为const右值必须也要加const

用一个变量为右值引用,变量可以取地址,该变量是左值,----------右值引用运算的变量是一个左值

左值引用与右值引用的比较

首先,对于内置类型来说,右值引用的意义不大,我们来把代码转到反汇编看一看

可以看出其实二者的底层执行的操作没有任何区别


让我们来操作自定义类型试试

//这个是我封装库里string实现的一个MYSTL::string类,内部是stl::string+一个计数器

int tmp = 1;
namespace MYSTL {
	class string {
	public:
		string() {
			_tmp = tmp++;
			std::cout << "这里调用了无参的构造函数,计数器:" << _tmp << std::endl;
		}
		string(const char* str) {
			_tmp = tmp++;
			std::cout << "这里调用了char* -> string的构造函数,计数器:" << _tmp << std::endl;
			_std_string = str;
		}
		string(const string& s) {
			_tmp = tmp++;
			std::cout << "这里调用了左值引用的构造函数,计数器:" << _tmp << std::endl;
			_std_string = s._std_string;
		}
		string(const string&& s) {
			_tmp = tmp++;
			std::cout << "这里调用了右值引用的构造函数,计数器:" << _tmp << std::endl;
			_std_string = s._std_string;
		}
		string& operator=(const char* str) {
			_std_string = str;
			std::cout << "这里调用了char* -> string赋值重载函数,计数器:" << _tmp << std::endl;
			return *this;
		}
		string& operator=(const string& s) {
			std::cout << "这里调用了左值引用的赋值重载函数,计数器:" << _tmp << std::endl;
			_std_string = s._std_string;
			return *this;
		}
		string& operator=(const string&& s) {
			std::cout << "这里调用了右值引用的赋值重载函数,计数器:" << _tmp << std::endl;
			_std_string = s._std_string;
			return *this;
		}
	protected:
	private:
		int _tmp;
		std::string _std_string;
	};
}

基于上面的类进行如下测试

int main() {
	MYSTL::string s1 = "abcdef";	// 默认构造
	MYSTL::string s2;				// 左值赋值
	s2 = s1;
	MYSTL::string s3(s1);			// 左值构造
	MYSTL::string s4(std::move(s1));// 右值构造
	MYSTL::string s5;				// 右值赋值
	s5 = MYSTL::string("123456");


	system("pause");
}

可以看出传入参数不同,类也会执行不同的重载函数

右值引用的意义与优势

参数为右值的拷贝构造一般被称为移动构造,参数为右值的赋值重载函数一般被称为移动赋值

根据右值的定义,右值是取不出地址的值,显然我们不能直接在产生右值的作用域拿右值来进行运算,因为我们不能直接使用右值,所以,移动构造和移动重载都可以使用浅拷贝!!!

执行如下代码

int main() {
	std::list<MYSTL::string> l;

	l.push_back("1");
	l.push_back("12");
	l.push_back("123");
	std::cout << std::endl;

	MYSTL::string s1, s2, s3;
	s1 = "1", s2 = "12", s3 = "123";
	l.push_back(s1);
	l.push_back(s2);
	l.push_back(s3);
	std::cout << std::endl;

	system("pause");
	return 0;
}

可以看出移动构造插入3个元素一共需要进行三次浅拷贝三次深拷贝

左值构造插入3个元素一共需要进行六次深拷贝 差距还是蛮大的


完美转发

现对于一个T类型的数据来说 , 它可以是:>1.T         2.const T         3. T&         4.const T&         5.T&&        6.const T&&

如果现在我要操作这个数据,难道我要写六个不同参数模板吗?当然可以,但是这未免也太太太太丑陋了 

完美转发就是用来解决这个问题的

//语法
template<typename T>
void PerfectForward(T&& t) {
 Fun(t);
}

请注意函数名中的&&不是右值引用的意思而是完美转发的意思

std::forward 完美转发在传参的过程中保留对象原生类型属性
using namespace std;
void Fun(int& x) {
	cout << "void Fun(int& x)" << endl;
}
void Fun(const int& x) {
	cout << "void Fun(const int& x)" << endl;
}void Fun(int&& x) {
	cout << "void Fun(int&& x)" << endl;
}void Fun(const int&& x) {
	cout << "void Fun(const int&& x)" << endl;
}
template<typename T>
void PerfectForward(T&& t) {
	Fun(std::forward<T>(t));
}
int main()
{
	int a = 0;
	const int b = 0;
	PerfectForward(a);
	PerfectForward(b);
	PerfectForward(0);
	const int d = 0;
	PerfectForward(move(d));
	system("pause");
	return 0;
}

这样就可以通过一个模板来管理全部类型函数


  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值