• 左值 右值 一


    https://en.cppreference.com/w/cpp/language/value_category

    值的类别

    任何C++的表达式(带有变量名、操作数等的操作符)都可以特征化为两个相互独立的属性:类型和值类别。每一个表达式都有非引用类型,每一个表达式都严格属于三个几本值类别中的一个:prvalue(纯右值) xvalue(亡值)和lvalue(左值)

    • glvalue(“generalized” lvalue)(泛左值)是一个表达式,它的求值决定了一个对象,位运算或者函数;

    • prvalue(“pure” rvalue)(纯右值)是一个表达式,表达式有下面两种:

      • 计算一个操作符的运算对象,或者一个void表达式(这个右值没有结果对象)
      • 初始化一个对象或者位字段(这个纯右值被称作有一个结果对象)。除了decltype,所有的类和数组都有纯右值结果对象,就算被丢弃也是有的。这个结果对象可以是一个变量,一个用new表达式创建的对象,一个通过临时实例化创建的临时对象,或者是他们中的一个成员。
    • xvalue(an “eXpiring” value)(到期的值,亡值)是一个泛左值,表明一个结构体或者位字段的资源可以被重新利用

    • lvalue(历史遗留称谓,表示可以出现在赋值表达式左边)(左值)是一个glvalue(泛左值),不是xvalue(亡值)

    • rvalue(历史遗留称谓,表示可以出现在赋值表达式右边)(右值)是一个prvalue(纯右值)或者xvalue(亡值)

    注意: 这个分类与C++以前的标准版本区别比较大,详情参考下面信息

    基本分类

    lvalue 左值

    如下表达式是左值表达式:

    • 变量名、函数名、模板形参的名字或者数据成员的名字,不论其类型,比如std::cin std::endl。即使变量的类型是右值的引用,由其名字组成的表达式仍然是左值表达式

    • 返回类型是左值引用的函数调用或者操作符重载,比如std::getline(std::cin, str) std::cout << 1 str1 = str2 ++it

    • a = b a += b a %= b,所有内置的赋值和混合赋值表达式

    • ++a --a,内置的前置自增和前置自减表达式

    • *p,内置的间接取值表达式

    • a[n] p[n],内置的下标索引表达式,当a[n]的一个操作对象是一个数组的lvalue(左值)时(since C++11)

    • a.m,一个对象成员的表达式,除了m是一个枚举类型的成员或者是一个非静态成员函数,或者a是一个右值,但是m是一个对象的非静态数据成员

    • p->m,内置的指针成员表达式,除了m是一个枚举成员或者非静态函数

    • a.*mp,对象的成员指针,a是一个lvalue(左值),mp是一个数据成员指针

    • p->*mp,内置的指向成员指针的指针表达式,mp是一个数据成员指针

    • a, b,内置的逗号表达式,b是一个lvalue(左值)

    • a ? b : c,对于某些b和c的三元条件表达式(比如,当它们都是lvalues(左值))

    • 一个字符串,比如Hello, world!

    • 转换成lvalue(左值)引用类型的转换表达式,比如static_cast<int&>(x)

    • 返回类型是一个函数左值引用的函数调用或者操作符重载表达式(since C++11)

    • 转换为函数rvalue(右值)引用的表达式,比如static_cast<void (&&)(int)>(x)(since C++11)

    特性

    • 与glvalue(泛左值)相同
    • 可以用内置的取址表达式取址:&++i &std::endl都是合法的
    • 可修改的lvalue(左值)可以用作内置赋值或者混合赋值表达式的左边
    • lvalue(左值)可以初始化lvalue(左值)引用;创建一个新的名字用来关联表达式定义的对象

    prvalue 纯右值

    下面的表达式是纯右值

    • 字面意思的数据(除了字符串之外,比如Hello),比如42 true nullptr

    • 返回值不是引用的函数调用或者重载操作符,比如str.substr(1, 2) str1 + str2 it++

    • a++ a--,内置的后置自增和后置自减表达式

    • a + b a % b a & b a << b以及所有内置的算术表达式

    • a && b a || b !a,所有内置的逻辑表达式

    • a < b a == b a >= b,所有内置的比较表达式

    • &a,内置的取址表达式

    • a.m,对象成员表达式,m是一个枚举成员或者非静态成员函数,或者a是一个rvalue(右值)并且m是一个非引用类型的非静态数据成员(until C++11)

    • p->m,内置的指针成员表达思,m是一个枚举成员或者非静态成员函数

    • a.*mp,对象成员指针表达式,mp是一个成员函数,或者a是一个rvalue(右值)并且mp是一个数据成员指针(until C++11)

    • p->*mp内置的指针成员的指针表达式,mp是指向成员函数的指针

    • a, b,内置逗号表达式,b是rvalue(右值)

    • a ? b : c,对于某些b和c的三元判断表达式

    • 非引用类型的转换,比如static_cast<double>(x) std::string{} (int)42`

    • this指针

    • 枚举项

    • 非类型模板参数,除非类型是类或者(since C++20)lvalue(左值)引用类型

    • lambda表达式,比如[](int x){ return x * x; }(since C++11)

    • requires表达式,比如requires (T i) { typename T::type; }(since C++20)

    • 特殊化的概念,比如std::equality_comparable<int>(since C++20)

    特性

    • 与rvalue(右值)一样

    • prvalue(纯右值)没有多态:它所标识的对象的动态类型,取决于表达式的类型

    • 非类非数组的prvalue(纯右值)不能被cv(const volatile)限定。(注意:一个函数调用或者转换表达式可以产生非类的prvalue(纯右值)的cv限定类型,但是cv限定很快就会被去除)

    • prvalue(纯右值)不能有非完整类型(除了void,或者在decltype中使用)

    • prvalue(纯右值)不能有抽象类类型或者数组类型

    xvalue 亡值

    下面的表达式是xvalue(亡值)表达式:

    • 返回类型是rvalue(右值)引用的函数调用或者重载的操作符,比如std::move(x)

    • a[n],内置的下标索引表达式,其操作对象是数组的rvalue(右值)

    • a.m,对象成员表达式,a是rvalue(右值),m是一个非索引类型的非静态的数据成员

    • a.*mp,对象的一个成员指针表达式,a是rvalue(右值)并且mp是数据成员指针

    • a ? b : c,对于某些b和c的三元条件表达式

    • 对于对象类型rvalue(右值)索引转换,比如static_cast<char&&>(x)

    • 任何确定临时对象的表达式,在临时实例化完成后(since C++17)

    特性

    • 与rvalue(右值)一样
    • 与glvalue(泛左值)一样

    与所有的rvalues(右值)一样,xvalues(亡值)可以绑定到rvalue(右值)引用;与所有的glvalues(泛左值)一样,xvalues(亡值)可以有多态性,非类的xvalues(亡值)可以有cv限定。

    混合分类

    glvalue 泛左值

    一个glvalue(泛左值)表达式可以是lvalue(左值)或者xvalue(亡值)

    特性

    • glvalue(泛左值)可以隐式的转化为prvalue(纯右值),比如lvalue(左值)到rvalue(右值),数组到指针,函数到指针的隐式转换

    • glvalue(泛左值)可以是多态的:对象的东太太类型不必是表达式的静态类型

    • glvalue(泛左值)可以有未完成的类型,只要表达式允许

    rvalue 右值

    一个rvalue(右值)表达式可以是prvalue(纯右值)也可以是xvalue(亡值)

    特性

    • rvalue(右值)的地址不能通过内置的取地址符进行操作:&int() &i++ &42 &std::move(x)都是非法的

    • rvalue(右值)不能放到内置的赋值操作符或者混合赋值操作符的左侧

    • rvalue(右值)可以用在初始化const类型的lvalue(左值)索引,这种情况下,这个rvalue(右值)对象的生命周期被延长为索引的作用域结尾

    • rvalue(右值)可以用作初始化rvalue(右值)索引,这种情况下,这个rvalue(右值)对象的生命周期被延长到索引的作用域结尾(since C++11)

    • 当用做函数实参,并且有两个重载的函数可用,一个是传递rvalue(右值)引用,另一个是传递const lvalue(左值)引用,rvalue(右值)将会绑定到rvalue(左值)引用的重载上(因此,当拷贝和移动构造函数都可用时,rvalue(右值)的参数调用移动构造函数,拷贝和移动赋值操作与此类似)(since C++11)

    特殊分类

    为决定成员的函数调用

    a.mf p->mf,当mf是非静态成员函数,并且表达式a.*pmf p->*pmf,pmf是成员函数指针,被认为是prvalue(纯右值)表达式,但是它们不能用作初始化引用,不能用作函数的参数。除了用作函数调用操作符左侧参数(比如(p->*pmf)(args)),不能用作其他任何目的

    void

    返回void的函数调用表达式,转换为void的表达式,抛出异常的表达式都被定义为prvalue(纯右值),但是它们不能用作初始化引用,或者函数参数。可以用在舍弃值的语境下(比如,在一行,作为逗号操作符的左侧操作数)和函数返回void的返回语句中。另外,抛出异常的语句可以应做三元运算符?:的第二个和第三个操作数

    void表达式没有结果对象(since C++17)

    位字段

    代表位字段的表达式(比如,a.m,a是一个结构体struct A { int m: 3; }的lvalue(左值))是一个glvalue(泛左值)表达式:他可以用作赋值操作符的左侧,但是它的地址不能获得,并且不能绑定非const lvalue(左值)引用。const lvalue(左值)引用或者rvalue(右值)引用可以通过位字段glvalue(泛左值)初始化,但是会有一个对于位字段的临时拷贝,不能直接绑定位字段

    历史

    CPL

    CPL是第一个为表达式引入值类别的:所有的CPL表达式都能以“右侧模式”求值,但是只有一些表达式在“左侧模式”有意义。当在右侧模式求值时,表达式被当作求值的规则(右侧的值,或者右值)。当在左侧求值时,表达式被给定一个明确的地址(左侧的值或者左值)。左和右在这里表示赋值左侧和赋值右侧

    C

    C语言沿用了相似的分类,除了赋值角色不再那么重要:C语言的表达式被归类为左值表达式和其他(函数和非对象值),左值表示定义一个对象的表达式,一个定位器的值

    C++98

    在2011之前的C++沿用了C的模式。但是恢复了非左值表达式名字为右值,令函数为左值,增加了可以绑定引用到左值上,但是只有const引用可以绑定到右值上。一些非左值C语言表达式在C++中成为了左值表达式

    C++11

    随着移动语义添加到C++11,值的分类被重新定义,用来区分表达式的两种独立属性

    • 拥有身份:用来确定表达式是否与另一表达式有同一实体,比如通过比较对象或者函数的地址(直接或间接的)

    • 可被移动:移动构造函数,移动赋值操作或者另一个重载的函数实现了移动语义,可以绑定到这个表达式

    在 C++11中:

    • 拥有身份且不可被移动的表达式被称作左值 (lvalue)表达式;
    • 拥有身份且可被移动的表达式被称作亡值 (xvalue)表达式;
    • 不拥有身份且可被移动的表达式被称作纯右值 (prvalue)表达式;
    • 不拥有身份且不可被移动的表达式无法使用

    拥有身份的表达式被称作“泛左值 (glvalue) 表达式”。左值和亡值都是泛左值表达式。

    可被移动的表达式被称作“右值 (rvalue) 表达式”。纯右值和亡值都是右值表达式。

    C++17

    C++17 中,某些场合强制要求进行复制消除,而这要求将纯右值表达式从被它们所初始化的临时对象中分离出来,这就是我们现有的系统。要注意,与 C++11 的方案相比,纯右值已不再是可被移动。

    版权声明:本文版权归作者所有,如需转载,请标明出处

  • 相关阅读:
    VS2010/MFC编程入门之十四(对话框:向导对话框的创建及显示)
    VS2010/MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)
    Tomcat架构解析(四)-----Coyote、HTTP、AJP、HTTP2等协议
    Tomcat架构解析(三)-----Engine、host、context解析以及web应用加载
    Tomcat架构解析(二)-----Connector、Tomcat启动过程以及Server的创建过程
    Tomcat架构解析(一)-----Tomcat总体架构
    springboot深入学习(四)-----spring data、事务
    springboot深入学习(三)-----tomcat配置、websocket
    springboot深入学习(二)-----profile配置、运行原理、web开发
    springboot深入学习(一)-----springboot核心、配置文件加载、日志配置
  • 原文地址:https://www.cnblogs.com/studywithallofyou/p/14569859.html
Copyright © 2020-2023  润新知