当前位置: 首页 > news >正文

【重学c++primer】第五章第二节 深入浅出:左值和右值

文章目录

  • 左值右值
    • 传统的左值和右值划分
      • glvalue
      • prvalue
      • xvalue
      • 总结
    • 左值和右值的转换
      • 左值转右值
    • decltype

左值右值

传统的左值和右值划分

  1. 左值:英文为 left value, 简写lvalue
  2. 右值:英文为 right value, 简写rvalue

一个左一个右, 这个左右的判定是针对什么呢?
实际上是针对等号来实现判定的

int x;
x = 3;

x = 3这个表达式,x可以放在左边,也可以放在右边,这个表达式的含义就是把3赋予x
如果我们此时写3 = x,这样是无意义的,因为我们并不能把x赋予3

因此我们把能放在等号左边的称之为左值,相对应的,不能放在等号左边的称为右值

以上是c语言定义的左值和右值,但是c++呢,对c语言的左值和右值进行了拓展
实际上,左值不一定能放在左边,右值也可能放在等号左边

我们一直说左值右值,但是这个概念其实并不是针对某个对象/数值
什么意思呢?
在上面的代码片段中,int x, x是一个对象,也是一个变量,你说x是左值还是右值呢?这种说法本身就是错误的
因为只有这个对象作为表达式存在且同时对这个对象所表达的表达式进行求值之后,得到的结果是左值还是右值

因此:左值和右值是针对表达式或者表达式的求值结果

划分可见下图:
!img

是一个树形结构,其中就有l value和rvalue

glvalue

  • glvalue: generalized lvalue, 泛左值,是一个左值的扩展的集合,泛左值是一个表达式,这个表达式的求值结果能确定一个对象,位域或函数

具体来看:

x = 3;

这个代码片段是一个语句,语句里有一个赋值表达式,赋值表达式里还包含了两个子表达式:x3
通常来说,我们需要对赋值语句进行求值,那么需要先对x*和3**进行求值

对x求值的结果是啥?
这句话的含义并不是说,我要访问x的内存,获取其中的值,而是说广义的求值,而是说,我们需要获得x关联的那块内存, 这两者是有区别的

那么回过来理解泛左值,也就是说,我们通过这个内存,来确定这个x,这就是泛左值,简单来说,就是一种标识的作用
再比如:

int y;
y = 3;

和上面的代码片段几乎一样,只是赋值的对象不一样,一个是处理x对应的内存,一个是处理y所对应的内存
也就是说,x和y都是glvalue

prvalue

纯右值:和泛左值相对,也是一个表达式,但是这个表达式符合以下的两种情况之一

  • 作为某个运算符的操作数或者void表达式(例如返回void的函数)
  • 初始化某个对象/位域

先看第一点:

x = 3;

赋值表达式有两个操作数,一个是x,一个是3
其中x是gvalue, 3是prvalue
为什么呢?
因为这个3只能作为运算符的操作数来使用,你总不能这样写吧:3 = x

3只能做一般意义上的操作数,类似3 + 2, 3 / 2这种

再看第二点:

int x = 3;

这个语句是合法的,但是这个语句里不再是赋值操作符,而是初始化操作, 表示我们要执行拷贝初始化

此时这个3,只能用于初始化这个对象
3和x不同,3只能有上述的两种用法,但是x却可以标识一块内存地址

再看一个例子

struct Str
{};int main()
{int{};Str{};
}

上面的代码片段,都是构造出一个临时对象,这种临时对象,也被用来操作符的操作数或者用于初始化,因此也归属于纯右值

xvalue

将亡值:expiring, 其含义为:代表其资源能够被重新使用的对象或者位域的泛左值

举个例子:

#include <iostream>
#include <vector>using namespace std;int main()
{vector<int> x;return 0;
}

这个x能保存int类型的数据,将这些数据放在一串连续的内存中

那么这和将亡值又有什么关系呢?

比如说:

void func(vector<int>&& par)
{}int main()
{vector<int> x;func(std::move(x));return 0;
}

std::move的含义是转换成将亡值
这表明了,后续的代码,不会对x中包含的资源做任何行为,因为x即将死亡

总结

  • glvalue:标识对象,其中包含了lvalue和xvalue
    • lvalue: 也是标识一个对象/位域/函数,其次,不是xvalue, 也就是说,一个gvalue并不会即将死亡, 资源还是属于对象,别人无法偷走,那么就是lvalue
  • rvalue:要么是一个将亡值,确实标识一个对象/函数/位域,但是很快就会消亡(xvalue)或者作为操作符的操作数/初始化操作的操作数(prvalue)

那么回过头来:在c++中,左值不一定能放在等号左边,右值也可能放在等号左边
前半句:

const int x = 3;
const int y = 2;

定义了一个常量x,这个常量作为表达式处理,表达式求值以后,得到的是左值还是右值呢?很显然是一个左值
因为x的确标识了一个对象, x和y是两个不同的对象, 所以x属于glvalue

那么x是将亡值吗?
显然不是,因为我们后续还是可以使用x,因此,x属于glvalue的lvalue
那么我们肯定不能x = 4做这种操作,因为x是常量,不能放在等号的左边,不能被修改

后半句:

struct Str
{};
int main()
{int x = int{};Str y = Str{};
}

上面的代码片都是使用纯右值来初始化一个左值

那么,有意思的来了:

Str () = Str();
// Str {} = Str();
// Str {} = Str{}
// Str () = Str{};

!img

我们上面说到,这个临时对象属于纯右值,但是如你所见,这可以放在等号的左边
首先,这个临时对象没有标识某个对象/位域/函数,因此不属于glvalue, 因此属于rvalue

左值和右值的转换

左值转右值

int x = 3;
int y = x;
y = x;x + y;

从表达式的角度来理解,用3初始化x,并用x初始化y
注意到没有?上面说到,纯右值的作用有一条就是初始化
而且纯右值刚好还有一个作用是作为操作符的操作数

但是x和y显然是一个左值,但是这个代码又是合法的,并且和我上面的有冲突,我讲错了吗?
显然不是的,是因为,左值可以转换成右值
在一个表达式中的某个地方需要一个右值,那么我们可以提供一个左值,编译器会自动转换成一个右值

再看一个临时具体化的例子(Temporary Materializetion):从Prvalue到xvalue的转换

struct Str
{int x;
};int main()
{Str();  // 纯右值Str().x;    // xvalue
}

有意思,太有意思了
.操作符前面是一个临时对象,纯右值,后面是一个成员x, 它的含义是从临时对象中获取特定的部分(x)

也就是说,此时Str()可以理解为一种广义的对象,类似于之前代码片段的x或者y
Str()标识了某块内存,这个时候,我们就不能简单的理解为:

  • 初始化
  • 操作符的操作数
    这种简单的划分了

而是说,把它视为一个将亡值,因为将亡值属于glvalue,这样才能确定一个对象/位域/函数

还有其他例子吗?

void func(const int& par)
{}int main()
{func(3);return 0;
}

这个代码片段是合法的,我们用3来初始化par,从引用的角度来理解,引用是要绑定到某个具体的对象上面的,但是3是一个纯右值
我们并不认为3标识了一个对象,但是这个代码片段又是合法的
其实就是因为Temporary Materializetion, 把3转换成了一个将亡值

decltype

当我们理解了左值和右值的概念以后,再回头看decltype
当时是从类型推动的角度来讨论的,见【重学c++Primer】第二章

现在我们从左值右值的角度来看看, decltype对表达式的处理

  • prvalu -> T
  • lvalue -> T&
  • xvalue -> T&&

这里使用c++ insights来查看decltype的推导过程
!img

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • LabVIEW VI 多语言动态加载与运行的实现
  • Cesium天空盒子(Skybox)制作(js代码)和显示
  • C语言中的函数sscanf()用法
  • Golang基于DTM的分布式事务TCC实战
  • Golang | Leetcode Golang题解之第343题整数拆分
  • 16.2 TensorFlow 与 Keras 基础
  • 图表:调用FluentUI中的折线图散点图和饼状图
  • 八股之数据库
  • 无缝融入,即刻智能[二]:Dify-LLM平台(聊天智能助手、AI工作流)快速使用指南,42K+星标见证专属智能方案
  • 第二章 pytorch回归问题
  • Java、python、php版的企业单位考勤打卡管理系统的设计与实现(源码、调试、LW、开题、PPT)
  • 深度学习·Pytorch
  • Java TCP练习1
  • 部署 K8s 图形化管理工具 Dashboard
  • 【与C++的邂逅】--- 类和对象(上)
  • .pyc 想到的一些问题
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 10个最佳ES6特性 ES7与ES8的特性
  • CSS3 聊天气泡框以及 inherit、currentColor 关键字
  • flutter的key在widget list的作用以及必要性
  • js对象的深浅拷贝
  • Js基础知识(一) - 变量
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • mysql中InnoDB引擎中页的概念
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Redis 中的布隆过滤器
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • windows-nginx-https-本地配置
  • 聊聊flink的BlobWriter
  • 你不可错过的前端面试题(一)
  • 前端之Sass/Scss实战笔记
  • 前端知识点整理(待续)
  • 系统认识JavaScript正则表达式
  • 鱼骨图 - 如何绘制?
  • Java数据解析之JSON
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​数据结构之初始二叉树(3)
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • #Linux(权限管理)
  • #stm32整理(一)flash读写
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (03)光刻——半导体电路的绘制
  • (AngularJS)Angular 控制器之间通信初探
  • (二)斐波那契Fabonacci函数
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (十二)Flink Table API
  • (数据大屏)(Hadoop)基于SSM框架的学院校友管理系统的设计与实现+文档
  • (一)认识微服务
  • (转)视频码率,帧率和分辨率的联系与区别
  • .gitignore文件设置了忽略但不生效
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .net core 的缓存方案
  • .NET Core 中插件式开发实现
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅