c++左值右值详解

左值右值

左值和右值: 一个亲和的定义

左值指向一个内存地址, 右值什么都不指向.
通常右值生存周期很短, 左值的要长一些.
这样想会很有意思,就是把左值比作一个容器, 而右值是容器内的东西.

int x = 666;

666是一个右值,一个数字(严格来说是一个文字常量)没有特定的内存地址,除了程序运行时的一些临时寄存器. 这个数字赋给了变量x. 一个变量是有特定的内存地址的, 所以他是一个左值. c++声明, 一个赋值语句要求一个左值作为左操作数, 所以这个例子这是完全合法的.

有了一个左值x. 我们就可以这样做

iny* y = &x;

这里我们通过取地址运算符&获取x的内存地址并且把它放进y中. 它接受左值参数并生成右值, 这又是一个完全合法的操作: 在赋值语句的左边, 我们有一个左值(变量), 在右边有一个取地址运算符产生的右值.
但是我们不能像下面这样做:

int y;
666 = y;

这很明显. 但是真正的技术原因是666作为一个文字常量,一个右值, 是没有一个特定的内存地址的, 所以我们无法把y赋值给任何地方.
运行的化GCC会告诉我们

error: lvalue required as left operand of assignment
# 左值要求作为赋值语句的左操作数  

他说的是对的, 赋值语句的左操作数通常要求的是左值,而在我们的程序中, 我们使用了一个右值666.
我们也不能这样做

int* y = &666;

GCC提供了如下:

error: lvalue required as unary '&' operand`
# 左值要求作为一元运算符`&`的操作数.

他又是对的, 取地址操作符&希望的是一个左值作为参数, 因为只有一个左值才有&操作符需要操作的地址.

函数返回左值和右值

我们知道一个赋值语句的左操作数必须是一个左值. 因此, 像下面的这个函数肯定会抛出lvalue required as left operand of assignment, 左值要求作为一个赋值语句的左操作数

int setValue()
{
    return 0;
}

setValue() = 3;

很清楚,setValue()返回的是一个右值(临时数字6), 不能作为一个赋值语句的左操作数, 现在如果一个函数返回的是左值会发生什么呢?

int global = 100;
int& setGlobal()
{
    return global;
}

setGlobal() = 400;

这是合法的,因为setGlobal返回的是一个引用. 一个引用是指向现有内存地址(变量global)的东西, 所以它能够被赋值. 注意这里的&: 这不是取地址运算符, 它定义了这个函数的返回类型(一个引用)

从函数中返回左值的能力看起来相当模糊, 但是当你做一些高级的事情, 比如实现一些运算符的重载却是很有用的. 在以后的章节中会有更多内容.

左值到右值的转换

一个左值实可以转换为右值的: 这是完全合法的, 并且经常发生. 让我们以+运算符作为例子. 根据c++规范, 它拿两个右值作为参数并返回一个右值.

int x = 1;
int y = 3;
int z = x + y;

等一下: xy都是左值, 但是加法运算符需要的不是两个右值吗? 答案很简单,xy经历了隐式的左值到右值的转换. 很多其他的操作符也执行这样的转换, 如减法,加法和除法.

左值引用

右值能够被转化为左值吗? 不能. 它不是一种技术上的局限,不过编程语言就是这样设计的

int y = 10;
int &yref = y;
yref++; // y is 11 now

你可以说yrefint&类型的: 一个指向y的引用. 他被称为左值引用. 现在你能够通过引用类型yref来改变y的值.
我们知道一个引用必须指向一个在内存中已经存在的对象, 也就是一个左值, 在这里y的确是已经存在了的, 所以代码能够正确运行.
现在,如果我简化这个过程, 尝试直接将10赋值给我们的引用, 没有对象去保存他

int& yref = 10; // 这样可以吗?

在右边我们有一个临时的量, 一个需要被存在左值某个位置中的右值.
在左边我们有一个必须指向已经存在对象的引用(左值).
但是10作为一个数值的常数, 换句话说, 没有一个特定的内存地址, 再换句话说,一个右值, 这就与引用的思想发生冲突了.
如果你这样想, 禁止从右值转化为左值, 一个数字常数(右值)应该变成左值以便被引用. 如果我们允许这样,那么你就能够通过引用改变数字常数的值, 完全没有任何意义不是吗?更重要的是,如果一旦数值消失, 引用将指向什么呢?
下面的例子也是同样的道理

void fnc(int& x)
{}

int main()
{
    fuc(10); //这样是错的
    // 下面是对的
    // int x = 10;
    // fnc(x);
}

我们通过一个临时的右值(10)传递给一个需要引用类型参数的函数, 但是我们不能将右值转化为左值.有一个解决办法就是创建一个临时变量来存储这个右值,然后我们把这个临时变量传递进去,非常方便.

上面的编译时会报错

error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

意思是说引用不是const, 也就是常量, 但是根据语言规范,允许将一个const的左值绑定到右值, 所以下面的片段是对的

const int& ref = 10;

当然下面的也是对的

void fnc(const int& x)
{}
int main()
{
    fnc(10);
}

背后的原理很简单, 文字常量10是volatile类型的,不知道什么时候就过期了, 所以引用他是没有意义的, 让我们将引用本身设置为const的, 这样它指向的内容就不能改变了,然后就解决了修改右值的问题. 同样的, 这不是技术的局限,而是c++工作人员选择的避免愚蠢问题的方式.

这使得非常常见的c++的习惯用法, 函数中参数通过接收常量引用来接受值成为可能,就像我们前面的代码中写的那样,避免了不必要的临时对象的拷贝和构造.

其实在背后, 编译器会为你创建一个隐藏的变量(一个左值)用来存储原始的文字常量,并且这个绑定这个隐藏变量与你的引用,. 这和我们上面手动的做法基本相似,例如:

//这样写
const int& ref = 10;

//编译器为你做:
int __hidden_val = 10;
const int& ref = __hidden_val;
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值