C++左值右值引用问题浅析

前言

关于C++调用程序传回来的临时变量假如我们要引用它,由于这个变量是开在栈空间的,如果要得到它的引用势必会造成某些内存的非法访问问题,C++根据这个问题将其定义为左值右值问题。以下我转载了一篇译文,觉得讲的十分精彩。最后有自己的总结以及一些示例代码方便理解。


转载

以下转自https://blog.youkuaiyun.com/xuwqiang1994/article/details/79924310

翻译至https://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c/
C/C++编程中不是经常出现术语(左值)和rvalue(右值),但是一旦出现,它们的语意就不是特别清晰。最经常看到它们的地方是在编译错误和警告信息中。比如,用gcc编译下面的程序:

int foo() {
    return 2; }

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

你会得到:

test.c: In function 'main':
test.c:8:5: error: lvalue required as left operand of assignment

是的,这段代码不是合法的并且不是你想写的,但是那个错误信息提到了lvalue,一个通常在C/C++教程中不能找到的术语。另一个例子就是用g++编译下面的代码:

int& foo()
{
   
    return 2;
}

现在那个错误为:

testcpp.cpp: Infunction 'int& foo()':
testcpp.cpp:5:12: error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

再次,那个错误信息提到了难以理解的rvalue。那么在C/C++中lvalue和rvalue意味着什么?这是我打算 在这篇文字中探讨的。

一个简单定义

这个部分先给出lvalue和rvalue的一个简单定义。文章下面将会详细描述。
lvalue(locator value)代表一个在内存中占有确定位置的对象(换句话说就是有一个地址)。
rvalue通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的lvalue的定义,rvalue是在不在内存中占有确定位置的表达式。

基本例子

上面的术语定义可能不是特别清楚,所以马上看一些简单例子是重要的。
假设我们定义了一个整形变量并且给它赋值:

int var;
var = 4;

赋值运算符要求一个lvalue作为它的左操作数,当然var是一个左值,因为它是一个占确定内存空间的对象。另一方面,下面的代码是无效的:

4 = var;        //ERROR!
(var + 10) = 4; //ERROR!

常量4和表达式var+1都不是lvalue(它们是rvalue)。它们不是lvalue,因为都是表达式的临时结果,没有确定的内存空间(换句话说,它们只是计算的周期驻留在临时的寄存器中)。因此给它们赋值没有语意-这里没有地方给它们赋值。
因此现在应该清楚了第一个代码片段的错误信息。foo返回一个临时的rvalue。尝试给它赋值,foo()=2,是一个错误;编译器期待在赋值运算符的左部分看到一个lvalue。
不是所有的对函数调用结果赋值都是无效的。比如,C++的引用(reference)让这成为可能:

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] 赋值是合法的因为非const的重载运算符 std::map::operator[] 返回一个可以被赋值的引用。

可修改的左值

开始在C语言中左值定义,它字面上意味着“合适作为赋值的左边部分”。然而,之后C标准中添加了const关键字后,这个定义不得不重新定义。毕竟:

const int a = 10; //‘a’是一个左值
a = 10;           //但是它不能被赋值

因此需要更深层次的重定义。不是所有的左值都能被赋值。这些可以称为可修改的左值。正式的,C99标准定义可修改左值为:

[…] 一个左值没有数组类型,没有不完全类型,没有const修饰的类型,并且如果它是结构体或联合体,则没有任何const修饰的成员(包含,递归包含,任何成员元素的集合)。

左值和右值的转换

通常来说,语言构造一个对象的值要求右值作为它的参数。例如,二元加运算符 ‘+’ 要求两个右值作为它的参数并且返回一个右值:

int a = 1;     //a是一个左值
int b = 2;     //b是一个左值
int c = a + b; //+需要右值,所以a和b都转换成右值,并且返回一个右值

从先前分析可以看到,ab都是左值。因此,在代码第三行,它们经历了一次从左值到右值的转换。所以的左值不能是数组,函数或不完全类型都可以转换成右值。
另一个方向的转换呢?右值可以转换成左值吗?当然不能!根据它的定义这将违反左值的语义[1]。
当然,这并不意味着左值不能通过更加显式的方法产生至右值。例如,一元运算符‘*’(解引用)拿一个右值作为参数而产生一个左值作为结果。考虑下面有效的代码:

int arr[] = {
   1, 2};
int* p = &arr[0];
*(p + 1) = 10;    //对的:p+1是一个右值,但是*(p+1)是一个左值

相反的,一元取地址符 ‘&’ 拿一个左值作为参数并且生成一个右值:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小胡同的诗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值