首页 > 编程语言 >【重学c++primer】第五章第二节 深入浅出:左值和右值

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

时间:2024-08-18 16:52:37浏览次数:10  
标签:右值 int 左值 c++ 对象 Str 表达式

文章目录

左值右值

传统的左值和右值划分

  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

标签:右值,int,左值,c++,对象,Str,表达式
From: https://blog.csdn.net/qq_51931826/article/details/141302022

相关文章

  • C++实现计算器(菜鸡版*2)
    我写了两种,都是支持小数的(默认从左到右,请自行解括号)别喷我这个很菜,还要用户自己解括号。大部分计算器不都这样吗(包括Windows自带的),而且我还编了一个可以直观的看到公式的。话虽如此,但我还是会努力编出更好用的计算器的喜欢就收藏一下吧第一种:数字/运算符一个一个输入代码:......
  • C++判断素数模板
    首先是朴素方法代码:#include<bits/stdc++.h>usingnamespacestd;intnum;boolcheck(intnum){if(num<2){returnfalse;}for(inti=2;i<=sqrt(num);i++){if(num%i==0){returnfalse;}}returntr......
  • C++(>>)
    目录1.位移操作符(BitwiseRightShiftOperator)2.输入流操作符(StreamExtractionOperator)3.在OpenCV中的重载操作符4.在类中的重载操作符总结在C++中,>>是一个多功能的操作符,根据上下文的不同可以有不同的含义和用途。下面详细介绍其在各种场景中的用法。1.位移操作......
  • C++(cv::VideoCapture::open())
    在OpenCV中,cv::VideoCapture类用于从视频文件或摄像头捕获视频流。cap.open()是cv::VideoCapture类的一个成员函数,用于打开视频源。以下是关于cap.open()的详细介绍:函数定义cv::VideoCapture::open有两个主要的重载形式:boolopen(intindex)这个重载版本用于打开一......
  • 【C++二分查找】1954. 收集足够苹果的最小花园周长
    本文涉及的基础知识点C++二分查找LeetCode1954.收集足够苹果的最小花园周长给你一个用无限二维网格表示的花园,每一个整数坐标处都有一棵苹果树。整数坐标(i,j)处的苹果树有|i|+|j|个苹果。你将会买下正中心坐标是(0,0)的一块正方形土地,且每条边都与两条坐......
  • C++可控制线程
    大家好,本人是C++新人qing。我学习编程也快十年了,这一年来我用C++写了一些程序,有了一些新奇的想法。我写了一些诸如“C语言存储变长字符串”、“C++可控制线程对象”、“TCP通信接收任意长度字符串”的代码。这些都是我的拙作,希望能够分享给大家,主要是新人可以练练手,有意见也......
  • 鼠标键盘控制c++
     感觉鼠标控制挺好玩的 要想完成鼠标的一系列控制,首先你需要一个头文件:#include<windows.h> 以下是鼠标单击左键的代码,可以做成子程序(我是背下来的):mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//按下左键Sleep(10);//要给一些应用反应时间mouse_event(MOUSEEVENTF_L......
  • 【全网独家】OpenCV C++ 图像处理实战 :多二维码识别(代码+测试部署)
    介绍在现代社会,二维码无处不在,从支付、物流到用户身份验证,二维码的应用极其广泛。本文将详细介绍如何使用OpenCV在C++环境下实现多二维码识别。我们将涵盖其应用场景、原理解释、算法流程图以及实际代码实现。应用使用场景仓储物流管理:快速扫描多个包裹上的二维码,实现高......
  • c++ builder哪个版本更好用
    1、当前,功能相对完全和成熟的是XE7。2、如果开发传统的程序,C++BUILDER2006最成熟轻量。二、可以难说哪个更好用,每个版本都有它自个的特点,典型的版的本个人理解供你参考:1、C++BUILDER4.0是BCB(C++BUILDER的简称)的第一个win下的版本,后继还有个小升级到C++BUILDER4.5,如果你想在......
  • C++入门篇一
    C++入门篇一一.缺省参数1.缺省参数的概念2.缺省参数分类二.函数重载1.函数重载概念2.函数重载代码举例三.引用1.引用的概念2.引用特性3.常引用4.使用场景(1).做参数(2).做返回值5.传值、传引用效率比较6.引用和指针的区别7.引用和指针的不同点一.缺省......