左值和右值

155 阅读7分钟

Hello 大家好,今天我们来学习左值和右值

1 左值和右值

• 左值是一个表示数据的表达式(如变量名或解引用的指针),一般是有持久状态,存储在内存中,我们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

• 右值也是一个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址

• 值得一提的是,左值的英文简写为lvalue,右值的英文简写为rvalue。传统认为它们分别是leftvalue、right value 的缩写。现代C++中,lvalue 被解释为loactor value的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,而 rvalue 被解释为 read value,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等,也就是说左值和右值的核心区别就是能否取地址。


#include<iostream>
using namespace std;
int main()
{
	// 左值:可以取地址
	// 以下的p、b、c、*p、s、s[0]就是常见的左值
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p = 10;
	string s("111111");
	s[0] = 'x';
	cout << &c << endl;
	cout << (void*)&s[0] << endl;
	// 右值:不能取地址
	double x = 1.1, y = 2.2;
	// 以下几个10、x + y、fmin(x, y)、string("11111")都是常见的右值
	//字面量常量
	10;
    
	//表达式求值过程中创建的临时对象
	x + y;
    
	//存储于寄存器中的变量:函数返回值
	fmin(x, y);
    
	//匿名对象是临时对象
	string("11111");
    
	//右值均不能取地址,会报错
	/*cout << &10 << endl;
	cout << &(x+y) << endl;
	cout << &(fmin(x, y)) << endl;
	cout << &string("11111") << endl;*/
	return 0;
}

2 左值引用和右值引用

• Type& r1 = x; Type&& rr1 = y; 第一个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同样的道理,右值引用就是给右值取别名。

• 左值引用不能直接引用右值,但是const左值引用可以引用右值

• 右值引用不能直接引用左值,但是右值引用可以引用move(左值)

• move是库里面的一个函数模板,本质内部是进行强制类型转换

• 需要注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值

• 语法层面看,左值引用和右值引用都是取别名,不开空间。从汇编底层的角度看下面代码中r1和rr1汇编层实现,底层都是用指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要然到一起去理解,互相佐证,这样反而是陷入迷途。

#include<iostream>

//move库文件
#include<utility>
using namespace std;
int main()
{
	// 左值:可以取地址
	// 以下的p、b、c、*p、s、s[0]就是常见的左值
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p = 10;
	string s("111111");
	s[0] = 'x';
	double x = 1.1, y = 2.2;

	// 左值引用给左值取别名
	int& r1 = b;
	int*& r2 = p;
	int& r3 = *p;
	string& r4 = s;
	char& r5 = s[0];

	// 右值引用给右值取别名
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	string&& rr4 = string("11111");

	// 左值引用不能直接引用右值,但是const左值引用可以引用右值
	const int& rx1 = 10;
	const double& rx2 = x + y;
	const double& rx3 = fmin(x, y);
	const string& rx4 = string("11111");

	// 右值引用不能直接引用左值,但是右值引用可以引用move(左值)
	int&& rrx1 = move(b);
	int*&& rrx2 = move(p);
	int&& rrx3 = move(*p);
	//move本质是进行强制类型转换,rrx4和rrx5效果一样
	string&& rrx4 = move(s);
	string&& rrx5 = (string&&)s;

	// b、r1、rr1都是变量表达式,都是左值
	cout << &b << endl;
	cout << &r1 << endl;
//变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值
	cout << &rr1 << endl;

	// 这里要注意的是,rr1的属性是左值,所以不能再被右值引用绑定,除非move一下
	int& r6 = r1;
	// int&& rrx6 = rr1;
	int&& rrx6 = move(rr1);
	return 0;
}

3 引用延长生命周期

右值引用可用于为临时对象延长生命周期,const 的左值引用也能延长临时对象生存期,但这些对象无法被修改。

#include<iostream>
using namespace std;
class result
{
public:
	result()
	{
		cout << "构造:result()"<<'\n';
	}
	~result()
	{
		cout << "析构:~result()"<<'\n';
	}
};
int main()
{
	std::string s1 = "Test";
	// std::string&& r1 = s1; // 错误:不能绑定到左值
	//临时对象的生命周期只有本行
	const std::string& s2 = s1 + s1; // OK:到 const 的左值引用延长生存期
	//引用后生命周期延长到r2的生命周期
	// r2 += "Test"; // 错误:不能通过到 const 的引用修改

	std::string&& s3 = s1 + s1; // OK:右值引用延长生存期

	s3 += "Test"; // OK:能通过到非 const 的引用修改
	std::cout << s3 << '\n';
	//我们来实践看一下
	//result()创建了一个临时对象
	result();

	//result()创建了一个临时对象,它是一个右值
	// 通过结果我们可以看到直到main函数结束时这个对象才调用了析构函数
	result&& r1 = result();
	//result()创建了一个临时对象,它是一个const左值引用
	const result& r2 = result();

	return 0;
}

result()创建了一个临时对象,result();语句执行完成后,临时对象的生命周期结束,调用析构函数

右值引用和const左值引用时,直到main函数结束时这个对象才调用了析构函数

4 左值和右值的参数匹配

• C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。

• C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const 左值引用),实参是右值会匹配f(右值引用)。

• 右值引用变量在用于表达式时属性是左值,这个设计这里会感觉跟怪,下一小节我们讲右值引用的使用场景时,就能体会这样设计的价值了

#include<iostream>
using namespace std;
void f(int& x)
{
	std::cout << "左值引用重载 f(" << x << ")\n";
}
void f(const int& x)
{
	std::cout << "到 const 的左值引用重载 f(" << x << ")\n";
}
void f(int&& x)
{
	std::cout << "右值引用重载 f(" << x << ")\n";
}
int main()
{
	int i = 1;
	const int ci = 2;
	f(i); // 调用 f(int&)
	f(ci); // 调用 f(const int&)
	f(3); // 调用 f(int&&),如果没有 f(int&&) 重载则会调用 f(const int&)
	f(std::move(i)); // 调用 f(int&&)

	// 右值引用变量在用于表达式时是左值
	int&& x = 1;
	f(x); // 调用 f(int& x)

	f(std::move(x)); // 调用 f(int&& x)
	return 0;
}

到此,左值后右值就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。

如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路

如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正

博语小屋将持续为您推出文章