C++编程:理解左值(lvalue)和右值(rvalue)

news2024/11/14 20:54:36

C++ 值的分类(Value Categories)

目录

1  概述

2  主要分类

1.1  左值(lvalue)

1.1.1  左值详情

1.1.2  左值属性

1.2  纯右值(prvalue)

1.2.1  纯右值详情

1.2.2  纯右值属性

1.3  将逝值(xvalue)

1.3.1  将逝值详情

1.3.2  将逝值属性

3  混合分类

3.1  泛型左值(glvalue)

3.1.1  泛型左值详情

3.1.2  泛型左值属性

3.2  右值(rvalue)

3.2.1 右值详情

3.2.2  右值属性


1  概述

         每一个C++ 表示达(带操作数的操作符、文字量、变量名、等等)都由两个独立属性刻画:类型(type)及类型值所属分类(type value category),值类别是编译器在表达式求值期间创建、复制和移动临时对象时必须遵循的规则的基础。每一个表达式都有一些非引用表达式,且每一个表达式都恰好归属于三种主要的值分类之一:纯右值(prvalue),将逝值(xvalue)和左值(lvalue)。这些分类的关系如下图:

 

主次分类为:

(1) 泛型左值(glvalue——“generalized” lvalue): 是一个表达式,其求值(evaluation)决定了对象,位域,或函数的身份。

(2) 纯右值(prvalue——“pure”rvalue):是一个表达式,其求值

a. 计算内置运算符的操作数的值(此类 prvalue 没有结果对象),或

b. 初始化一个对象(这种prvalue被认为有一个结果对象)。 

    结果对象可以是变量、由 new 表达式创建的对象、由临时实现创建的临时对象或其成员。请注意,非 void 废弃值表达式具有结果对象(实现的临时对象)。此外,每个类和数组 prvalue 都有一个结果对象,除非它是 decltype 的操作数;

(3) 将逝值(xvalue——“eXpiring”value,即将消亡(或正在消亡)的值): 是一个泛型左值,表示一个对象的资源可以重用,将逝值表达式具有一个地址,该地址不供程序访问,但可用于初始化右值引用,从而提供对表达式的访问。示例包括返回右值引用的函数调用,以及数组下标、成员和指向成员的指针表达式,其中数组或对象是右值引用。;

(4) 左值(lvalue):左值是非将逝值的泛型左值其具有一个程序可访问的地址(注:左值就是可通过直接取其内存地址进行操作的对象,而右值没有可供程序访问的地址)。从历史上看,之所以这样称呼,是因为左值可以出现在赋值表达式的左侧一般来说,情况并非总是如此

void foo();

 

void baz()

{

    int a; // 表达式`a` 是一个左值,因为可通过取其内存进行直接访问

    a = 4; // 正确,可以出现在表达式左侧 

    int &b{a}; // 表达式 `b` 是一个左值

    b = 5; //正确,可以出现在表达式左侧  

    const int &c{a}; // 表达式 `c` 是一个左值,因为可通过取其内存进行直接访问

    c = 6;           // 错误, 只读引用不能赋值 

    // 表达式 `foo` 是一个左值,类为函数也可以通过取其地址进行直接访问

    // 可通过内置取地址运算符取其地址

    void (*p)() = &foo;

 

    foo = baz; // 错误, 不能将函数赋值给函数地址

}

(5) 右值(rvalue):是纯粹的右值或将逝值。从历史上看,之所以这样称呼,是因为右值可以出现在赋值表达式的右侧。一般来说,情况也并非总是如此:

#include <iostream>
 
struct S
{
    S() : m{42} {}
    S(int a) : m{a} {}
    int m;
};
 
int main()
{
    S s;
 
    // 表达式 `S{}` 是一个纯粹右值
    // 可以出现在赋值表达式的右侧
    s = S{}; // 0初始化
 
    std::cout << s.m << '\n';
 
    // 表达式 `S{}` 是一个纯粹的右值
    // 也可以用在表达式的左边
    std::cout << (S{} = S{7}).m << '\n';
}

输出:

42

7

注意:此分类法在过去的 C++ 标准修订中经历了重大变化,详情请参阅下面的历史记录。

尽管这些术语有这样的名字,但它们是对表达式进行分类,而不是对值进行分类。

#include <type_traits>
#include <utility>
 
template <class T> struct is_prvalue : std::true_type {};
template <class T> struct is_prvalue<T&> : std::false_type {};
template <class T> struct is_prvalue<T&&> : std::false_type {};
 
template <class T> struct is_lvalue : std::false_type {};
template <class T> struct is_lvalue<T&> : std::true_type {};
template <class T> struct is_lvalue<T&&> : std::false_type {};
 
template <class T> struct is_xvalue : std::false_type {};
template <class T> struct is_xvalue<T&> : std::false_type {};
template <class T> struct is_xvalue<T&&> : std::true_type {};
 
int main()
{
    int a{42};
    int& b{a};
    int&& r{std::move(a)};
 
    // 表达式 `42` 是纯右值
    static_assert(is_prvalue<decltype((42))>::value);
 
    //表达式 `a` 是一个左值
    static_assert(is_lvalue<decltype((a))>::value);
 
    //表达式`b`是一个左值
    static_assert(is_lvalue<decltype((b))>::value);
 
    //表达式`std::move(a)` 是一个将逝值
    static_assert(is_xvalue<decltype((std::move(a)))>::value);
 
    // 变量类型 `r` 是一个右值引用
    static_assert(std::is_rvalue_reference<decltype(r)>::value);
 
    //变量类型`b` 是一个左值引用
    static_assert(std::is_lvalue_reference<decltype(b)>::value);
 
    //表达式`r`是左值
    static_assert(is_lvalue<decltype((r))>::value);
}

2  主要分类

1.1  左值(lvalue)

1.1.1  左值详情

下面的表达式是左值表达式:

(1) 变量、函数、模板参数对象(自 C++20 起)或数据成员的名称,无论类型如何,例如 std::cin 或 std::endl。即使变量的类型是右值引用,由其名称组成的表达式也是左值表达式(但请参阅可移动表达式)。

void foo() {}
 
void baz()
{
    // `foo` 是一个左值
    // 可通过直接取其内存地址进行访问
    void (*p)() = &foo;
}
 
struct foo {};
 
template <foo a>
void baz()
{
    const foo* obj = &a;  // `a` 是一个左值, 模板参数对象
}

(2)  函数调用或重载运算符表达式,其返回类型为左值引用,例如 std::getline(std::cin, str)、std::cout << 1、str1 = str2 或 ++it。

int& a_ref()
{
    static int a{3};
    return a;
}
 
void foo()
{
a_ref() = 5;  // `a_ref()` 是一个左值, 函数调用其返回类型是一个左值引用
}

(3) a = b、a += b、a %= b 以及所有其他内置赋值和复合赋值是左值表达式。

(4) ++a 和 --a,内置的前增量和前减量表达式是左值表达式。

(5) *p,内置间接左值表达式。

(6) a[n] 和 p[n],内置下标表达式,其中 a[n] 中的一个操作数是数组左值(自 C++11 起)。

(7) a.m,对象表达式的成员,除非 m 是成员枚举器或非静态成员函数,或者 a 是右值且 m 是对象类型的非静态数据成员。

struct foo
{
    enum bar
    {
        m //成员枚举器
    };
};
 
void baz()
{
    foo a;
    a.m = 42; // , 需要左值作为赋值的左操作数
}
struct foo
{
    void m() {} // 非静态成员函数
};
 
void baz()
{
    foo a;
 
    // `a.m` 是一个纯右值, 因此不能通过内置取地址运算符取得其地址
    void (foo::*p1)() = &a.m; // 
 
    void (foo::*p2)() = &foo::m; // OK: 成员函数指针
}

(8)  p->m,指针表达式的内置成员,除非 m 是成员枚举器或非静态成员函数。

(9)  a.*mp,指向对象表达式的成员的指针,其中 a 是左值,mp 是指向数据成员的指针。

(10)  p->*mp,指针表达式的内置指向成员的指针,其中mp是指向数据成员的指针。

(11) a,b,内置逗号表达式,其中b是左值。

(12) a ? b : c,对于特定的 b 和 c 的三元条件表达式(例如,当两者都是同一类型的左值时,但请参阅定义以了解详细信息)。

(13) 字符串文字量,例如 "Hello, world!";

(14)转换为左值引用类型的转换表达式,例如 static_cast<int&>(x) 或 static_cast<void(&)(int)>(x);

(15) 左值引用类型的非类型模板参数;

template <int& v>
void set()
{
    v = 5; // 模板参数是左值
}
 
int a{3}; //静态变量, 固定地址在编译时已知
 
void foo()
{
    set<a>();
}

(16) 函数调用或重载运算符表达式,其返回类型是函数的右值引用(C++11及以上版本)。

(17) 强制转换表达式为函数类型的右值引用,例如 static_cast<void(&&)(int)>(x) (C++11及以上版本)。

1.1.2  左值属性

(1) 与泛型左值相同(见下文)。

(2) 左值的地址可由内置地址运算符获取:&++i[1] 和 &std::endl 是有效表达式。

(3) 可修改的左值可用作内置赋值和复合赋值运算符的左侧操作数。

(4) 左值可用于初始化左值引用;这会将新名称与表达式标识的对象关联起来。

1.2  纯右值(prvalue)

1.2.1  纯右值详情

下面的表达式是纯右值表达式:

(1) 字面量(字符串字面量除外),例如 42、truenullptr

(2) 函数调用或重载运算符表达式,其返回类型为非引用,例如 str.substr(1, 2)、str1 + str2 或 it++。

(3) a++ 和 a--,内置后增和后减表达式。

(4) a + b、a % b、a & b、a << b 和所有其他内置算术表达式。

(5) a && b、a || b、!a,内置逻辑表达式。

(6) a < b、a == b、a >= b 和所有其他内置比较表达式。

(7) &a,内置地址表达式。

(8) a.m,对象成员表达式,其中 m 是成员枚举器或非静态成员函数[2] 。

(9) p->m,指针表达式的内置成员,其中 m 是成员枚举器或非静态成员函数[2] 。

(10) a.*mp,指向对象成员的指针表达式,其中 mp 是指向成员函数的指针[2] 。

(11) p->*mp,指向指针表达式的内置成员的指针,其中 mp 是指向成员函数的指针[2] 。

(12) a, b,内置逗号表达式,其中 b 是纯右值。

(12) a ? b : c,针对特定 b 和 c 的三元条件表达式(详情请参阅定义)。

(13) 转换为非引用类型的强制转换表达式,例如 static_cast<double>(x)、std::string{} 或 (int)42。

(14) this 指针。

(15) 枚举器。

(16) 标量类型的非类型模板参数。

template <int v>
void foo()
{
    // 非左值, `v` 是一个类型为int标量参数模板
    const int* a = &v; // 
 
    v = 3; // : 需要左值作为赋值的左操作数
}

(17) lambda 表达式,例如 [](int x){ return x * x; }(C++11及以上版本)。

(18) 需要表达式,例如需要 (T i) { typename T::type; }(C++11及以上版本)。

(19) 概念的特化,例如 std::equality_comparable<int>(C++11及以上版本)。

1.2.2  纯右值属性

(1) 与右值相同(见下文)。

(2) 纯右值不能是多态的:它表示的对象的动态类型始终是表达式的类型。

(3)  非类非数组右值不能是 cv修饰的的,除非它被具体化以绑定到对 cv 修饰类型的引用(C++17及以上版本)。(注意:函数调用或强制转换表达式可能导致非类 cv 修饰类型的 右值,但 cv 修饰符通常会立即被删除。)

(4) 右值不能具有不完整类型(void 类型除外,见下文,或在 decltype 说明符中使用时)。

(5) 右值不能具有抽象类类型或其数组。

1.3  将逝值(xvalue)

1.3.1  将逝值详情

    下面的表达式是将逝值表达式:

(1) a.m,对象成员表达式,其中 a 是右值,m 是对象类型的非静态数据成员。

(2) a.*mp,对象成员指针表达式,其中 a 是右值,mp 是数据成员指针。

(3) a, b,内置逗号表达式,其中 b 是 将逝值。

(4) a ? b : c,针对特定 b 和 c 的三元条件表达式(详情请参阅定义)。

(5) 函数调用或重载运算符表达式,其返回类型为对象的右值引用,例如 std::move(x)(C++11及以上版本)。

(6) a[n],内置下标表达式,其中一个操作数是数组右值(C++11及以上版本)。

(7) 强制转换为对象类型的右值引用的表达式,例如 static_cast<char&&>(x) (C++11及以上版本)。

(8) 任何指定临时对象的表达式,在临时实现之后(C++17及以上版本)。

(9) 可移动的表达式。(C++23及以上版本)。

1.3.2  将逝值属性

(1) 同右值(下述)。

(2) 同泛左值(下述)。

具体来说,与所有右值一样,将逝值绑定到右值引用,并且与所有泛左值一样,将逝值可以是多态的,并且非类 将逝值可以是 cv 限定的。

#include <type_traits>

 

template <class T> struct is_prvalue : std::true_type {};

template <class T> struct is_prvalue<T&> : std::false_type {};

template <class T> struct is_prvalue<T&&> : std::false_type {};

 

template <class T> struct is_lvalue : std::false_type {};

template <class T> struct is_lvalue<T&> : std::true_type {};

template <class T> struct is_lvalue<T&&> : std::false_type {};

 

template <class T> struct is_xvalue : std::false_type {};

template <class T> struct is_xvalue<T&> : std::false_type {};

template <class T> struct is_xvalue<T&&> : std::true_type {};

 

// Example from C++23 standard: 7.2.1 Value category [basic.lval]

struct A

{

    int m;

};

 

A&& operator+(A, A);

A&& f();

 

int main()

{

    A a;

    A&& ar = static_cast<A&&>(a);

 

    // 具有返回类型左值引用的函数调用是将逝值    static_assert(is_xvalue<decltype( (f()) )>::value);

 

    // 对象表达成员, 对象是将逝值, `m` 是非静态数据成员

    static_assert(is_xvalue<decltype( (f().m) )>::value);

 

    // 右值引用转换表达式

    static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value);

 

    // 运算符表达式,其返回类型是对象的右值引用

    static_assert(is_xvalue<decltype( (a + a) )>::value);

 

    // 表达式`ar`左值, `&ar` 有效

    static_assert(is_lvalue<decltype( (ar) )>::value);

    [[maybe_unused]] A* ap = &ar;

}

3  混合分类

3.1  泛型左值(glvalue)

3.1.1  泛型左值详情

泛型左值表达式要么是左值,要么是将逝值。

3.1.2  泛型左值属性

(1) 泛左值可以通过左值到右值、数组到指针或函数到指针的隐式转换隐式转换为纯右值。

(2) 泛左值可以是多态的:它所标识的对象的动态类型不一定是表达式的静态类型。

(3) 泛左值可以具有不完整类型,只要表达式允许。

3.2  右值(rvalue)

3.2.1 右值详情

右值表达式要么是纯右值,要么是将逝值。

3.2.2  右值属性

(1) 右值的地址不能由内置地址运算符获取:&int(),&i++[3],&42 和 &std::move(x) 等操作无效。

(2) 右值不能用作内置赋值或复合赋值运算符的左侧操作数。

(3) 右值可用于初始化 const 左值引用,在这种情况下,右值标识的临时对象的生存期将延长,直到引用的范围结束。

(4) 右值可用于初始化右值引用,在这种情况下,右值标识的临时对象的生存期将延长,直到引用的范围结束(C++11 及以上版本)。

(5) 当用作函数参数并且该函数有两个重载可用时,一个采用右值引用参数,另一个采用对 const 参数的左值引用,则右值绑定到右值引用重载(因此,如果复制和移动构造函数都可用,则右值参数会调用移动构造函数,复制和移动赋值运算符也是如此) (C++11 及以上版本)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2090508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day51 | 117. 软件构建(拓扑排序)47. 参加科学大会 dijkstra(朴素版)

语言 117. 软件构建 117. 软件构建 题目 题目描述 某个大型软件项目的构建系统拥有 N 个文件&#xff0c;文件编号从 0 到 N - 1&#xff0c;在这些文件中&#xff0c;某些文件依赖于其他文件的内容&#xff0c;这意味着如果文件 A 依赖于文件 B&#xff0c;则必须在处理…

【STM32】通用定时器TIM(时钟源选择与更新中断)

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 通用定时器简介 定时器时钟使能 选择时基单元时钟源 内部时钟源 外部时钟源 时基单元初始化 更新中断使能 定时器使能 定时器中断代码 Timer.h Timer.c 获取计数值 TIM(Time…

【王树森】RNN模型与NLP应用(7/9):机器翻译与Seq2Seq模型(个人向笔记)

Machine Translation Data 做机器学习任务的第一步都是处理数据&#xff0c;我们首先需要准备机器翻译的数据。由于我们是学习用途&#xff0c;因此拿一个小规模数据集即可&#xff1a;http://www.manythings.org/anki/下面的数据集中&#xff1a;一个英语句子对应多个德语句子…

Spring MVC执行流程

整体流程&#xff1a; 用户向前端控制器发送请求前端控制器接收到请求后调用处理映射器处理器映射器找到具体的处理器&#xff0c;生成处理器对象以及处理器拦截器&#xff0c;再一起返回给前端控制器然后前端控制器调用处理器适配器处理器适配器调用具体的处理器处理器适配器…

element plus el-upload上传组件,自动上传,记录解决:本地报404,文件找不到问题

问题&#xff1a; 解决问题&#xff1a; 重点是&#xff1a;加入action"#"和:http-request"uploadHttpRequest" <el-uploadv-loading"isLoading"ref"upload"v-model"fileList":multiple"multiple"action&quo…

《PCI Express体系结构导读》随记 —— 第II篇 第7章 PCIe总线的数据链路层与物理层(1)

前言中曾提到&#xff1a;本章重点介绍PCI Express总线的数据链路层与物理层。 PCIe总线的数据链路层处于事务层和物理层之间&#xff0c;主要功能是保证来自事务层的TLP在PCIe链路中的正确传递&#xff0c;为此数据链路层定义了一系列数据链路层报文&#xff0c;即DLLP。数据链…

AI实践与学习8-AI Agent Workflow助力解题和验证答案置信度

背景 之前在试着提高解题正确率&#xff0c;目标100%&#xff0c;发现外部知识不足仅依靠大模型的话比较困难。而试题人工生产成本巨大。 本质因为大模型生成内容会有幻觉特点&#xff0c;也就是说解答的试题正确性不太好评判&#xff0c;直接解答试题生产场景不太可控。 后…

市场纷乱中,沃尔沃坚守长期主义之道,用“P1+P4”解决用户痛点3A品质,插混王者——沃尔沃插混让性能成为插混必要条件

“造车就像西天取经&#xff0c;明确的方向、实现目标的能力&#xff0c;内心的坚持缺一不可”,本届成都国际车展上&#xff0c;来自沃尔沃汽车集团全球高级副总裁袁小林的一句话可谓是振聋发聩&#xff0c;向沉迷玩转“流量密码”的车企&#xff0c;向因各种噱头而迷茫的用户发…

RISC-V全志D1sCVBS套件

此开发板的任何问题都可以在我们的论坛交流讨论 https://forums.100ask.net/c/10-category/75-category/75 硬件简述 D1s主板 主板如下&#xff1a; D1s板载功能 板载功能有 XR829 WIFI蓝牙模组芯片&#xff0c;Bluetooth支持标准蓝牙与 低功耗蓝牙&#xff0c;Wifi 支持…

论被动元数据的弊端,以及主动元数据的技术优势

元数据是企业数据生态系统中不可或缺的组成部分&#xff0c;核心在于为数据本身提供完整的描述性信息&#xff0c;包括数据来源、数据结构、语义含义、物理位置、所有权归属、创建时间、流转路径等关键要素&#xff0c;是企业理解、开发、消费和应用数据的基石。 元数据管理&a…

毕设创新点之一:基于GD32/STM32的AI模型部署-github库

将AI模型成功部署到边缘MCU中&#xff0c;常常受限于MCU的计算峰值和内存峰值的限制&#xff0c;部署较为困难&#xff0c;目前有一个将AI算法MCU部署到GD32系列MCU中的宝藏的开源库。 项目网址&#xff1a;HomiKetalys/gd32ai-modelzoo: Provide deployable deep learning mo…

springweb获取请求数据、spring中拦截器

SpringWeb获取请求数据 springWeb支持多种类型的请求参数进行封装 1、使用HttpServletRequest对象接收 PostMapping(path "/login")//post请求//spring自动注入public String login(HttpServletRequest request){ System.out.println(request.getParameter("…

若依脚手架 创建一个系统 his医院信息管理系统

一、创建his-medicine模块 0) 在创建好的若依后端项目中创建一个maven模块his-medicine 1&#xff09;his模块的整合步骤 ①&#xff09;his的依赖 这个是若依项目所有系统模块都需要添加的依赖&#xff0c;domain和controller继承的类就在这里面。 <!-- 通用工具--><…

【位置编码】【Positional Encoding】直观理解位置编码!把位置编码想象成秒针!

【位置编码】【Positional Encoding】直观理解位置编码&#xff01;把位置编码想象成秒针&#xff01; 你们有没有好奇过为啥位置编码非得长成这样&#xff1a; P E ( p o s , 2 i ) s i n ( p o s 1000 0 2 i / d m o d e l ) P E ( p o s , 2 i 1 ) c o s ( p o s 1000 …

基于yolov8的手势识别0-9检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的手势识别0-9检测系统是一个利用深度学习技术&#xff0c;特别是YOLOv8算法&#xff0c;实现对手势&#xff08;0至9的数字手势&#xff09;进行快速、准确识别的系统。YOLOv8以其高效的性能和准确性&#xff0c;在实时性要求较高的手势识别领域表现出…

ant-design-vue v-decorator用法

笔者一直在做后端&#xff0c;最近公司要求&#xff0c;帮助前端同时写一下前端页面。这里也记录下一些新学的知识&#xff0c;帮助大家避坑 在ant-design中&#xff0c;v-decorator可以实现双向绑定与表单验证。即如果你使用v-decorator 你可以不用使用v-model。 <a-form…

[000-01-015].第03节:SpringBoot中数据源的自动配置

我的后端学习大纲 SpringBoot学习大纲 1.数据访问流程&#xff1a; 2.搭建数据库开发场景&#xff1a; 2.1.导入JDBC场景&#xff1a; 2.2.分析自动导入的内容&#xff1a; 2.3.分析为何没有导入数据库驱动&#xff1a; 1.因为人家也不知道我要用啥数据库&#xff0c;所以在自…

92. UE5 RPG 使用C++创建GE实现灼烧的负面效果

在正常游戏里&#xff0c;有些伤害技能会携带一些负面效果&#xff0c;比如火焰伤害的技能会携带燃烧效果&#xff0c;敌人在受到伤害后&#xff0c;会接受一个燃烧的效果&#xff0c;燃烧效果会在敌人身上持续一段时间&#xff0c;并且持续受到火焰灼烧。 我们将在这一篇文章里…

PTA L1-028 判断素数

L1-028 判断素数&#xff08;10分&#xff09; 本题的目标很简单&#xff0c;就是判断一个给定的正整数是否素数。 输入格式&#xff1a; 输入在第一行给出一个正整数N&#xff08;≤ 10&#xff09;&#xff0c;随后N行&#xff0c;每行给出一个小于的需要判断的正整数。 …

vscode里调试python3.6的配置

vscode里需要降级如下插件&#xff1a; ● Python v2022.8.1 ● Pylance v2022.6.30 ● Python Debugger v2023.1.XXX (pre-release version | debugpy v1.5.1)