在 C++ 中,rvalue(右值)和 lvalues(左值)是表达式类型的两个基本分类,它们定义了表达式与程序中对象的关系:
Lvalue(左值)
- 定义:左值是指那些具有内存地址的表达式,它可以出现在赋值表达式的左边,即可以被赋值。
- 特点:
- 可以被取地址(
&
)操作符获取其内存地址。 - 可以出现在赋值表达式的左侧。
- 通常与变量或对象相关联,如
int a = 10;
中的a
。
- 可以被取地址(
Rvalue(右值)
- 定义:右值是指那些不具有内存地址或者其地址是临时的、不需要被赋值的表达式。
- 特点:
- 不能被取地址操作。
- 通常作为赋值表达式的右侧,不能出现在左侧。
- 包括字面量、表达式的结果等,如
3 + 4
或std::vector<int>()
。
一些关键点:
- 临时对象:在 C++ 中,函数调用的结果、操作符的返回值等都是临时对象,它们是右值。
- const 和非常量类型:对于非 const 类型的对象,左值必须具有持久生存期,而右值可以是临时的。对于 const 类型的对象,左值和右值的区别在于是否能被赋值。
- 移动语义:C++11 引入了右值引用(使用
&&
声明),它允许程序员利用右值的移动语义来提高程序性能,通过移动资源而不是复制资源来避免不必要的复制。 - std::move:C++11 中的
std::move
函数可以将左值强制转换为右值,以启用移动语义。但它不会改变对象的实际类型,只是告诉编译器该对象可以被视为右值。 - 生命周期:左值的生命周期通常是由程序员管理的,而右值的生命周期是由编译器管理的,它们通常在表达式结束时结束。
示例:
int createInt() {
return 10; // 这里返回的是一个临时的右值
}
int a = 5; // a 是一个左值
int b = createInt(); // 将右值赋给左值 b
int& lvref = a; // 正确,a 是左值,可以被取地址
int& rvref = createInt(); // 错误,createInt() 返回右值,不能被取地址
int* p = &a; // 正确,a 是左值,可以被取地址
// int* p2 = &createInt(); // 错误,不能取右值的地址
a = 20; // 正确,a 是左值,可以赋值
// createInt() = 20; // 错误,右值不能出现在赋值表达式的左侧
int&& rvalueRef = 10; // 正确,10 是字面量右值
int&& rvalueRef2 = a; // 正确,即使 a 本身是左值,也可以绑定到右值引用
在 C++11 及以后的版本中,右值引用和移动语义的引入使得 rvalue 和 lvalue 的区分变得更加重要,因为它们直接影响到资源的管理和程序的性能。