C++的左值(lvalue)和右值(rvalue)
背景
lvalue(左值)、rvalue(右值)这些术语来自C语言(当然,C语言的术语习惯也可能来自更早的语言,Gemfield就不追溯了)。在C语言中,lvalue和rvalue中的l和r是left和right,分别代表着赋值表达式(等号)的左边和右边。并且:
- 出现在等号左边的必须是lvalue;
- rvalue只能出现在等号的右边。
g1 = g2
其中等号左边的g1必须是lvalue,g2可以是lvalue或者rvalue。但其实把lvalue中的l看成是location就容易理解,就是lvalue是有直接的memory location的,而rvalue没有。比如:
int gem;
gem = 7030;
gem是lvalue,我们可以通过&gem来获得gem的memory location;而7030则是rvalue,我们无法获得它的内存地址。
C++的到来
C++语言的一些特性就改变了前述规则。
1,class类型
上述的规则在C++中就变得不准确了,首先就是class类型。在前述小节中我们得知,rvalue是没有memory的(备注:对于一些大的复杂的rvalue,其实也是隐式占用memory的,但对于外部用户来说,假定它没有占用),但当到了C++的class时代后,class类型的rvalue是会占用memory的:
struct Gemfield{int x;int y;
};
Gemfield f(){return {7,19};
}
函数f返回的依然是rvalue,但此时的rvalue已经是个默认的class类型的对象,它是占用内存的(不然为啥会有移动语义,好了这里先不展开)。
2,const
有了const后,不是所有的左值都可以出现在赋值表达式的左边了。比如:
const int gem = 7030;
gem = 1002; //error: assignment of read-only variable ‘gem'
gem是lvalue,但不能出现在等号左边了。
3,新的引用规则
C++新增了一条规则:reference可以绑定到lvalue,但只有reference to const 可以绑定到rvalue。具体来说:
- reference to T的引用只能绑定到T类型的lvalue上,比如下面的代码就是错误的(7030是rvalue):
int& gem = 7030; //错误
- reference to const T却不一定非得绑定到T类型的lvalue上,只要表达式产生的值可以转换成T类型即可。比如下面两句代码都正确,因为7030这个表达式产生的值(也即7030)可以转换为int或者double,于是c++编译器就创建了一个临时对象(第一句是临时int,第二句是临时double对象),然后将gem引用绑定到该临时对象上:
const int& gem = 7030;
const double& gem = 7030;
这样的行为正是函数形参经常为reference to const T的重要原因。reference to non-const 形参只接收non-const的实参;而reference to const的形参可以接收const或者non-const的实参,不论接收哪种,reference to const都会产生一个non-modifiable lvalue。
Morden C++的到来
Morden C++,也就是C++11,更具体来说就是ISO/IEC 14882:2011,带来了新的value分类——其中最重要的一点便是带来了右值引用(rvalue reference)。这就意味着:
- 传统C++时代的引用,在morden c++(C++11及以后)中成为了左值引用(lvalue reference);
- Morden C++新引入的右值引用(rvalue reference)开创了一个全新的领域:移动语义。
左值引用使用&操作符,而右值引用使用&&操作符,比如:
int&& gem = 7030;
并且右值引用既可以作为函数形参,也可以作为函数返回值:
Gemfield&& f(int && g);
且morden c++又为reference增加了一条规则:rvalue reference只能绑定到rvalue上,甚至rvalue reference to const也只能绑定到rvalue上:
int gem = 7030;
int&& r = gem; //error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
const int&& rr = gem; //error: cannot bind rvalue reference of type ‘const int&&’ to lvalue of type ‘int’
继续说回移动语义(move semantics)。C++11在移动语义的基础上,将value类别重构为如下几类:
- 有名字,就是glvalue(generalized lvalue,包含lvalue和xvalue);
- 有名字,且不能被move,就是lvalue;
- 有名字,且可以被move,就是xvalue(eXpiring value,走到生命周期尽头的value,因此可以被move);
- 没有名字,且可以被移动,则是prvalue(pure rvalue)。
value类别 | 是否有名字 | 是否可move | 备注 |
---|---|---|---|
glvalue | 是 | generalized lvalue = lvalue + xvalue | |
lvalue | 是 | 否 | |
xvalue | 是 | 是 | eXpiring value |
prvalue | 否 | 是 | pure rvalue |
rvalue | 是 | rvalue = xvalue + prvalue |
其中lvalue、prvalue、xvalue是最基础的分类,而glvalue、rvalue是混合的:
- glvalue = lvalue + xvalue
- rvalue = prvalue + xvalue
1,lvalue
下列表达式是lvalue表达式:
- 变量的名字、函数的名字(注意不是函数调用)、数据成员的名字;
- 函数调用,且其返回值类型为lvalue reference,比如:
std::cout<<"gemfield";
str1 = str2;
++it;
- 内置的赋值表达式,比如:
a = b;
a += b;
a %=b;
- 内置的pre-increment、pre-decrement表达式(注意:是pre而不是post):
++a;
--a;
- 内置的间接寻址运算符(indirection expression):
*p;
- 内置的下标运算符(subscript expression):
a[n];
p[n];
- 类的数据成员:
a.m;
p->m;
- 逗号表达式(comma expression):
g,e; //e需要为lvalue
- 三元条件表达式:
g ? e : m; //e和m需要是lvalue
- 字符串:
"gemfield";
- cast表达式,且cast为lvalue reference type:
static_cast<int&>(x)
2,prvalue
下列表达式是prvalue表达式:
- 字符(注意不是字符串):比如'g'、'e'、true、nullptr、35;
- 函数调用,或重载运算符表达式,且返回值的类型为非引用,比如:
str.substr(1,2);
str1 + str2;
it++;
- 内置的post-increment和post-decrement表达式(注意不是pre):
a++;
a--;//想一下为什么下面的表达式是错的
(gem++)++ //error: lvalue required as increment operand.
(++gem)++ //正确
++(gem++) //error: lvalue required as increment operand
- 内置的算术运算表达式:a + b、a % b、 a & b、 a << b
- 内置的逻辑表达式:a && b、 a || b、 !a
- 内置的比较表达式:a < b、a == b、 a >= b
- 内置的取地址表达式:&a
- 类的非static成员函数:a.m、p->m
- 逗号表达式:a,b ,且b为rvalue;
- cast表达式,且cast为非引用类型,比如:
static_cast<double>(x);
std::string{};
(int)42;
- this指针;
- enumerator;
- lambda表达式:
[](int x){return x + 7030;};
- requires表达式:
requires (T i){typename T::type;};
3,xvalue
下列表达式是xvalue表达式:
- 函数调用,或者重载运算符表达式,且返回值类型为rvalue reference,比如:std::move(x);
- rvalue对象的non-reference类型的非静态数据成员:a.m;
- cast表达式,且cast为右值引用,比如:
static_cast<char&&>(x);
- temporary materialization时生成的临时对象:
struct S { int m; };
int i = S().m; // C++17标准:要访问对象的成员时,期望一个glvalue// S() prvalue 被转换为 xvalue
- temporary materialization还有其它很多情况,比如右值引用绑定到右值上时会创建xvalue。
再谈移动语义
先看一段代码:
string s1, s2, s3;
s1 = s2; //调用拷贝赋值
s1 = s2 + s3; //调用移动赋值,因为s2 + s3产生prvalue,前面讲过了
然后s2 + s3这个prvalue传递给move assignment operator的右值引用形参。假设move assignment operator的实现如下所示:
string& string::operator=(string&& rhs) {...string temp(rhs);...
}
那么代码中的string temp(rhs)是调用string类的拷贝构造函数呢,还是移动构造函数呢?答案是拷贝构造,因为rhs这个时候不是rvalue:rhs有名字,且预期在函数结束前它的生命都还在——这是lvalue!
但是有时候,我们编程时,就是想要move一个lvalue——因为程序员了解自己的意图(显然编译器不了解),比如:
template <typename T>
void swap(T& a, T& b){T temp(a);a = b;b = temp;
}
在函数第一行的T temp(a)中,编译器认为a还将继续有生命,但程序员知道过了这行后,a原有的资源已经不需要了(因为要用b填充了),那怎么在这一行调用移动构造而不是拷贝构造呢?这就相当于,如何让编译器知道a不再是个lvalue,而是个要过期的value——也即eXpiring value——也即xvalue——也即一种rvalue呢?那我们就需要做个转换。
前文讲过:“xvalue表达式的一种情况是:函数调用,或者重载运算符表达式,且返回值类型为rvalue reference,比如:std::move(x)”,这就是std::move的意义,可以将lvalue转换为xvalue(一种rvalue)。
总结
现在,C++不止有lvalue和rvalue了,而是:
C++的左值(lvalue)和右值(rvalue)相关推荐
- C语言的左值(lvalue)和右值(rvalue)的含义是什么?
C语言的左支(lvalue)和右值(rvalue)的含义是什么? (1)左值就是一个可被存储的单元,右值就是一个可被读取的数据. (2)左值必须是一个被明确了的内存存储单元,可以用来被赋值: ...
- c语言中字符串关于左值,关于左值lvalue和右值rvalue的一点理解
发现很多朋友对"lvalue"和"rvalue"理解有误,我先谈谈自己对此的一些理解,并期望能够引起更多朋友的广泛讨论.也算起到抛砖引玉的作用吧.引用:注:这里 ...
- c语言 变量的左值和右值,C++雾中风景10:聊聊左值,纯右值与将亡值
C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度.为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一 ...
- C++左值、右值、左值引用、右值引用
左值(lvalue)和右值(rvalue) 左值(lvalue):locator value,存储在内存中.有明确的的地址(可寻址)的数据 能够取地址,有名字的值就是左值 //左值引用 int a=1 ...
- C++/C++11中左值、左值引用、右值、右值引用的使用
C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...
- 的引用_左值、右值、左值引用、右值引用
[导读]:本文主要详细介绍了左值.右值.左值引用.右值引用以及move.完美转发. 左值和右值 左值(left-values),缩写:lvalues 右值(right-values),缩写:rvalu ...
- java左值与右值问题_[C++11]左值、右值、左值引用、右值引用小结
左值和右值 左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象 右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字. 比如 int a = b + c;,a 就是一个左值, ...
- 左值右值,左值引用和右值引用及其用途
目录 1.左值和右值 2.引用 (1)左值引用 (2)右值引用 3.左值引用的用途 (1)作为复杂名称变量的别名 (2)用于rangeFor循环 (3)避免复制大的对象 (4)参与函数中的参数传递 4 ...
- [c++]-c++中的左值和右值、左值引用和右值引用、万能引用和引用折叠及完美转发
1.左值和右值 1.1左值和右值定义 在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置.一般来说,右值是暂时和短暂的,而 ...
最新文章
- 解释个人计算机与多用户系统之间的区别,计算机导论问答题答案
- 某产品经理炫耀:3年跳槽3次,月薪从8k涨到38k,跳槽涨薪最快!
- 有关函数模板和类模板的说法
- Entity Framework 4.1 : 贪婪加载和延迟加载
- 自我训练——时间控制能力(四)
- 2 RepMLP:卷积重参数化为全连接层进行图像识别 (Arxiv)
- 全球互联网领域第一人!马云获福布斯终身成就奖
- 马尔可夫链的扩展 贝叶斯网络 (Bayesian Networks)
- springmvc配置拦截器
- linux运行关关采集器,杰奇小说2.3-自动采集-关关采集器高级版
- 【智慧城市】智能照明系统解决方案
- caffe框架学习(layer)
- Python爬虫之初窥Scrapy
- 阿里云STMP邮箱验证
- 信息系统项目管理重点:信息化发展原则和方向
- python+html实现前后端数据交互界面显示
- 个人面试问答题知识库(一)百面机器学习篇
- matlab 毕业答辩,MATLAB的答辩.ppt
- python简笔画程序_用python设计程序输生日判断星座,及星座简笔画。
- 如何把一组计算机做成云,旧电脑如何变成云电脑?进来了解下云电脑