Bootstrap

C++左值与右值の深思——万能引用与完美转发

传统艺能😎

小编是双非本科大一菜鸟不赘述,欢迎米娜桑来指点江山哦(QQ:1319365055)

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
这是我和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

左值与右值🤔

在 C++11 里,尤其突出了两个概念那就是左右值, = 左边就是左值, = 右边就是右值,但是其实左右值的药还藏的蛮深的,里面学问大着呢,记得之前 21 年字节的青训营就出过关于左右值的作业,叫手撕一个完美转发,一时半会儿磕不明白左右值搞混了就直接头疼。

左值是一个表示数据的表达式,如变量名或解引用的指针。 \color{red} {左值是一个表示数据的表达式,如变量名或解引用的指针。} 左值是一个表示数据的表达式,如变量名或解引用的指针。

左值可以被取地址,也可以被修改(const修饰的左值除外)。左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边:

int main()
{
   
	//下面 a、b、c、*a 都是左值
	int* a = new int(0);
	int b = 1;
	const int c = 2;
	return 0;
}

右值也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等 \color{red} {右值也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等} 右值也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等等

右值不能被取地址,也不能被修改。右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边

int main()
{
   
	double x = 1.1, y = 2.2;

	//常见右值
	10;
	x + y;
	fmin(x, y);

	//错误示例(右值不能在赋值符号左边)
	//10 = 1;
	//x + y = 1;
	//fmin(x, y) = 1;
	return 0;
}

右值本质就是一个临时变量或常量值,比如代码中的10就是常量值,表达式 x+y 和函数 fmin 的返回值就是临时变量,这些都叫做右值。

这些临时变量和常量值并没有被实际存储起来,这也就是为什么右值不能被取地址的原因,因为只有被存储起来后才有地址。
但需要注意的是,这里说函数的返回值是右值,指的是传值返回的函数,因为传值返回的函数在返回对象时返回的是对象的拷贝,这个拷贝出来的对象就是一个临时变量

对于左值引用返回的函数来说,这些函数返回的就是左值,比如 string 类实现的 [] 运算符重载函数

namespace str
{
   
	//模拟实现string类
	class string
	{
   
	public:
		//[]运算符重载(可读可写)
		char& operator[](size_t i)
		{
   
			assert(i < _size); //检测下标的合法性
			return _str[i]; //返回对应字符
		}
		//...
	private:
		char* _str;       //存储字符串
		size_t _size;     //记录字符串当前的有效长度
		//...
	};
}
int main()
{
   
	str::string s("hello");
	s[3] = 'x';    //引用返回,支持外部修改
	return 0;
}

这里的 [] 运算符重载函数返回的是引用,因为它需要支持外部对该位置的修改,所以必须采用左值引用返回。之所以说这里返回的是一个左值,是因为这个返回的字符是被存储起来了的,存在 string 的 _str 对象中,因此这个字符可以被取到地址

左/右值引用🤔

C++98 中就有引用的语法,而 C++11 中新增了右值引用,为了进行区分,于是将 C++11 之前的引用就叫做左值引用,但是无论左值引用还是右值引用,本质都是给对象取别名

左值引用通过 & 来声明。:

int main()
{
   
	//下面 a、b、c、*a 都是左值
	int* a = new int(0);
	int b = 1;
	const int c = 2;

	//对上面左值的左值引用
	int*& ra = a;
	int& rb = b;
	const int& rc = c;
	int& va = *a;
	return 0;
}

右值引用通过 && 来声明:

int main()
{
   
	double x = 1.1, y = 2.2;
	
	//常见的右值
	10;
	x + y;
	fmin(x, y);

	//对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double rr3 = fmin(x, y);
	return 0;
}

需要注意的是,右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改,如果不想让被引用的右值被修改,可以用const修饰右值引用

int main()
{
   
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	const double&& rr2 = x + y;

	rr1 = 20;
	rr2 = 5.5; //报错
	return 0;
}

那么问题来了,左值引用可以引用右值吗?右值引用又可以引用左值吗?

左值引用不能引用右值,因为右值是不能被修改的,而左值引用是可以修改,这是典型的权限放大问题,但是 const 的左值引用可以引用右值,因为 const 能够保证被引用的数据不会被修改,维持了权限。

template<class T>
//const 左值引用引用右值
void 
;