左值与右值

oneNeko 于 2022-05-20 发布

左值右值的定义非常复杂,详细定义参见值类别 cppreference
理解 C/C++ 中的左值右值,可以参考这篇文章理解 C/C++ 中的左值和右值 这里只摘取部分

简单定义

这里我故意给出了一个左值右值的简化版定义。文章剩下的部分还会进行详细解释,

左值 (lvalue, locator value) 表示了一个占据内存中某个可识别的位置(也就是一个地址)的对象。

右值 (rvalue) 则使用排除法来定义。一个表达式不是左值就是右值。 那么,右值 是一个 表示内存中某个可识别位置的对象的表达式。

举例

上面的术语定义显得有些模糊,这时候我们就需要马上看一些例子。我们假设定义并赋值了一个整形变量:

int var;
var = 4;

赋值操作需要左操作数是一个左值var是一个有内存位置的对象,因此它是左值。然而,下面的写法则是错的:

4 = var;       // 错误!
(var + 1) = 4; // 错误!

常量4和表达式var + 1都不是左值(也就是说,它们是右值),因为它们都是表达式的临时结果,而没有可识别的内存位置(也就是说,只存在于计算过程中的每个临时寄存器中)。因此,赋值给它们是没有任何语义上的意义的——我们赋值到了一个不存在的位置。

int& foo()
{
    return 2;
}
/*
testcpp.cpp: In function 'int& foo()':
testcpp.cpp:5:12: error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
*/

那么,我们就能理解这个代码片段中的错误信息的含义了。foo返回的是一个临时的值。它是一个右值,赋值给它是错误的。因此当编译器看到foo() = 2时,会报错——赋值语句的左边应当是一个左值

然而,给函数返回的结果赋值,不一定总是错误的操作。例如,C++ 的引用让我们可以这样写:

int globalvar = 20;

int& foo()
{
    return globalvar;
}

int main()
{
    foo() = 10;
    return 0;
}

这里foo返回一个引用。引用一个左值,因此可以赋值给它。实际上,C++ 中函数可以返回左值的功能对实现一些重载的操作符非常重要。一个常见的例子就是重载方括号操作符[],来实现一些查找访问的操作,如std::map中的方括号:

std::map<int, float> mymap;
mymap[10] = 5.6;

之所以能赋值给mymap[10],是因为std::map::operator[]的重载返回的是一个可赋值的引用。

右值引用

参考资料 《effective modern C++》 // 待施工

右值引用 用 “&&” 表示,常用于移动语义完美转发,例如

const int&& a=10;
void func(const int&& x){

}

参考

值类别 cppreference
理解 C/C++ 中的左值右值