解密 C++ 中的左值(lvalue)与右值(rvalue)的核心内容

在 C++ 中,表达式(expression) 可以被归类为左值或右值。最简单的理解方式是:

  • 左值(lvalue): 能放在赋值号 = 左边的表达式,通常表示一个有名字、有内存地址、可以持续存在的对象。你可以获取它的地址。
  • 右值(rvalue): 不能放在赋值号 = 左边的表达式,通常表示一个临时的、没有名字、没有固定内存地址、生命周期短暂的值。你不能直接获取它的地址。

这种“能否被赋值”的粗略定义在 C++ 的发展中逐渐变得复杂,尤其是在 C++11 引入了右值引用和移动语义之后。但它仍然是一个很好的起点。

深入理解左值(lvalue)

左值是指具有**身份(identity)且可以被修改(modifiable)**的表达式(除非它是 const 左值)。这意味着它在内存中有确定的位置,你可以反复访问它,并且可以改变它的值(如果不是 const)。

左值的特点:

  1. 有内存地址:你可以用 & 运算符获取它的地址。
  2. 有身份:你可以区分出不同的左值对象。
  3. 持久性:它的生命周期通常不是语句结束就消失的。
  4. 可赋值:除非是 const 左值,否则可以出现在赋值号的左边。

常见的左值示例:

  • 变量名int a = 10; 这里的 a 就是一个左值。
    int x = 5; // x 是一个左值
    int* ptr = &x; // 可以取地址
    x = 10; // 可以被赋值
    
  • 函数返回的左值引用:如果一个函数返回类型是引用(T&const T&),那么函数调用的结果就是左值。
    int& get_value() {static int val = 42;return val;
    }
    // ...
    get_value() = 100; // get_value() 的结果是左值,可以被赋值
    int* p = &get_value(); // 可以取地址
    
  • 解除引用操作符 * 的结果*ptr
    int arr[] = {1, 2, 3};
    int* ptr = arr;
    *ptr = 0; // *ptr 是一个左值,可以被赋值
    
  • 下标运算符 [] 的结果arr[0]
    int arr[3];
    arr[0] = 10; // arr[0] 是一个左值,可以被赋值
    
  • 成员访问运算符 .-> 的结果:如果成员本身是左值。
    struct MyStruct { int data; };
    MyStruct s;
    s.data = 5; // s.data 是一个左值
    
  • 字符串字面量:虽然是常量,但它们存储在内存的某个位置,因此是左值。
    const char* str = "hello"; // "hello" 是一个左值(const char[6] 类型)
    

深入理解右值(rvalue)

右值是指那些生命周期短暂、没有独立身份(或者说身份不重要)、通常不能被修改的表达式。它们通常在表达式计算完成后就消失了。

右值的特点:

  1. 无内存地址(通常):不能用 & 运算符直接获取它的地址(除非是临时对象被绑定到 const 左值引用或右值引用)。
  2. 无身份:通常无法区分出不同的右值。
  3. 短暂性:它们的生命周期通常只在当前完整的表达式求值结束时。
  4. 不可赋值:不能出现在赋值号的左边。

常见的右值示例:

  • 字面量(Literal):除了字符串字面量(它是左值)以外的字面量都是右值。
    int x = 10; // 10 是一个右值
    std::string s = "world"; // "world" 是一个左值,但这里是构造函数参数
    
  • 算术、逻辑、位运算等结果:例如 a + ba && ba << 2 等。
    int a = 1, b = 2;
    int sum = a + b; // a + b 的结果是一个右值 (3)
    
  • 函数返回的非引用类型:如果一个函数返回类型是值类型(T),那么函数调用的结果就是右值。
    int get_sum(int x, int y) {return x + y;
    }
    // ...
    int result = get_sum(1, 2); // get_sum(1, 2) 的结果是一个右值 (3)
    // get_sum(1, 2) = 5; // 错误:不能给右值赋值
    // int* p = &get_sum(1, 2); // 错误:不能取右值的地址
    
  • 类型转换的结果:例如 static_cast<double>(some_int)
    int i = 5;
    double d = static_cast<double>(i); // static_cast<double>(i) 的结果是一个右值
    
  • Lambda 表达式本身:当定义一个 Lambda 时,它是一个右值。

为什么 C++ 需要区分左值和右值?

在 C++11 之前,主要是为了区分可以修改的实体临时的计算结果。但 C++11 引入了**右值引用(rvalue reference)移动语义(move semantics)**后,这种区分变得更加重要和复杂。

1. 移动语义(Move Semantics)

这是左值/右值区分最核心的应用场景。当一个对象是右值(临时的、即将被销毁的),我们就可以**“偷走”它的资源**(例如:指向动态分配内存的指针),而不是进行昂贵的深拷贝。这大大提高了程序性能,尤其是在处理大型对象时。

  • std::move():它是一个函数模板,它的作用是将一个左值强制转换为右值引用。它本身不做任何移动操作,只是告诉编译器“这个左值可以被当作右值来处理了”。
    std::vector<int> v1 = {1, 2, 3};
    std::vector<int> v2 = std::move(v1); // v1 被转换为右值,v2 会“偷走”v1 的资源// 此时 v1 处于有效但未指定状态(通常为空)
    

2. 完美转发(Perfect Forwarding)

在模板编程中,我们经常需要将参数原封不动地传递给另一个函数,保持其原始的左值/右值属性。

  • std::forward():它是一个条件转换,如果参数是左值,它就转发为左值;如果参数是右值,它就转发为右值。这对于编写泛型函数(如包装器)非常重要。
    template<typename T>
    void wrapper_func(T&& arg) { // T&& 在这里是万能引用/转发引用inner_func(std::forward<T>(arg)); // 保持 arg 的原始左值/右值属性
    }
    

C++11 引入的新的值类别:prvalue, xvalue, glvalue

为了更精确地描述值的属性,C++11 将值类别细化为以下三种:

  1. 左值(lvalue):拥有身份(地址)但不能被移动的对象。
  2. 纯右值(prvalue - pure rvalue):没有身份,可以被移动的对象。通常是字面量、函数返回值(非引用)、表达式的计算结果。
    • 例如:10a + bget_sum() 的返回值。
  3. 将亡值(xvalue - expiring value):拥有身份(地址),但资源可以被“偷走”(被移动)的对象。它通常是一个右值引用(例如 std::move(lvalue) 的结果)或一个绑定到右值引用的临时对象
    • 例如:std::move(some_lvalue) 的结果。

这三者之间的关系是:

  • 泛左值(glvalue - generalized lvalue) = 左值(lvalue) + 将亡值(xvalue)
    • 共同特点:都有身份(地址)。
  • 右值(rvalue) = 纯右值(prvalue) + 将亡值(xvalue)
    • 共同特点:都可以被移动。

(图片来源:Wikimedia Commons,概念图展示了 C++11 的值类别)

为什么引入将亡值(xvalue)?

因为在 C++11 之前,只有左值和右值。纯右值是匿名的临时对象,显然可以被移动。但如果一个具名对象(左值)我们也想让它移动而不是拷贝怎么办?std::move() 就是为此而生。它把一个左值变成了“将亡值”,告诉编译器:“这个东西虽然是个左值,但它马上就要‘死了’,你可以把它看作右值,去偷它的资源吧!”

总结

  • 左值:有地址,有身份,通常可修改,生命周期持久。能放在赋值号左边。
  • 右值:无地址(或地址不重要),无身份(或身份不重要),生命周期短暂。不能放在赋值号左边。

C++11 引入的 右值引用移动语义 极大地提升了处理临时对象的效率,避免了不必要的深拷贝。理解左值、右值、纯右值、将亡值这些概念,是编写高效、现代 C++ 代码的关键。在实际编程中,掌握 std::move()std::forward() 的正确使用,能让你更好地利用这些语言特性。

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

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

相关文章

MATLAB(2)选择结构

选择结构又可以叫做分支结构&#xff0c;它根据给定的条件是否成立&#xff0c;决定程序运行的方向。在不同的条件下执行不同的操作。 MATLAB可以用来实现选择结构的语句有三种&#xff1a;if语句、switch语句、try语句。 一.if语句 1.if语句 1.1条件为矩阵的情况 if语句的…

Ehcache、Caffeine、Spring Cache、Redis、J2Cache、Memcached 和 Guava Cache 的主要区别

主流缓存技术 Ehcache、Caffeine、Spring Cache、Redis、J2Cache、Memcached 和 Guava Cache 的主要区别&#xff0c;涵盖其架构、功能、适用场景和优缺点等方面&#xff1a; Ehcache 类型: 本地缓存&#xff08;JVM 内存缓存&#xff09; 特点: 轻量级&#xff0c;运行在 JV…

谷歌浏览器截图全屏扩展程序

以下是一些支持跟随鼠标滚轮滚动截图的谷歌全屏截图扩展程序插件&#xff1a; GoFullPage&#xff1a;这是一款专门截取整个网页的截图插件。安装后&#xff0c;点击浏览器右上角的图标或使用快捷键AltShiftP&#xff0c;插件就会自动开始滚动并捕获当前访问的网站&#xff0c…

专线服务器具体是指什么?

专线服务器主要是指在互联网或者是局域网中&#xff0c;为特定用户或者是应用程序所提供的专用服务器设备&#xff0c;专线服务器与传统的共享服务器相比较来说&#xff0c;有着更高的安全性和更为稳定的网络连接&#xff0c;下面我们就来共同了解一下专线服务器的具体内容吧&a…

Jenkins JNLP与SSH节点连接方式对比及连接断开问题解决方案

一、JNLP vs SSH 连接方式优缺点对比 对比维度JNLP&#xff08;Java Web Start&#xff09;SSH&#xff08;Secure Shell&#xff09;核心原理代理节点主动连接Jenkins主节点&#xff0c;通过加密通道通信&#xff0c;支持动态资源分配。Jenkins通过SSH协议远程登录代理节点执…

Git - Commit命令

git commit 是 Git 版本控制系统中核心的提交命令&#xff0c;用于将暂存区&#xff08;Stage/Index&#xff09;中的修改&#xff08;或新增/删除的文件&#xff09;永久记录到本地仓库&#xff08;Repository&#xff09;&#xff0c;生成一个新的提交记录&#xff08;Commit…

Android System WebView Canary:探索前沿,体验最新功能

在移动互联网时代&#xff0c;WebView作为Android系统的核心组件之一&#xff0c;承担着在原生应用中显示Web内容的重要任务。它不仅为用户提供了便捷的网页浏览体验&#xff0c;还为开发者提供了强大的混合式开发能力。Android System WebView Canary&#xff08;金丝雀版本&a…

kubernetes架构原理

目录 一. 为什么需要 Kubernetes 1. 对于开发人员 2. 对于运维人员 3. Kubernetes 带来的挑战 二. Kubernetes 架构解析 1. master 节点的组件 2. Node 节点包含的组件 3. kubernetes网络插件 三. kubeadm块速安装kubernetes集群 1. 基础环境准备(此步骤在三个节点都执…

服务器的安装与安全设置 域环境的搭建和管理 Windows基本配置 网络服务常用网络命令的应用 安全管理Windows Server 2019

高等职业教育计算机网络技术专业实训指导书 2025年目 录 实训的目的和意义 实训的具体目标及主要内容 实训完成后需要提交的内容 项目一 服务器的安装与安全设置 项目二 域环境的搭建和管理 项目三 Windows基本配置 项目四 网络服务 项目五 常用网络命令的应用 项目六…

Springcloud解决jar包运行时无法拉取nacos远程配置文件

问题描述 springcloud微服务&#xff0c;在idea中运行代码&#xff0c;能够正常拉去nacos上的配置文件&#xff0c;打包后&#xff0c;通过jar包启动 java -jar xxx.jar&#xff0c;出现错误&#xff1a;java.nio.charset.MalformedlnputException: Input length 1 问题原因…

【Leetcode刷题随笔】01. 两数之和

1. 题目描述 给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那 两个 整数&#xff0c;并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素不能使用两遍。 示例: 给定 nums [2, 7, 11,…

【机器学习深度学习】多层神经网络的构成

目录 一、神经网络模型的结构化组成方式 1. 最底层&#xff1a;神经网络模型 (Model) 2. 中间层&#xff1a;单个神经网络层 (Layer) 3. 最顶层&#xff1a;训练参数的细节 (Parameters & Variables) 二、关键理解要点 三、类比理解 场景一&#xff1a;工厂运作 场…

设计模式:揭秘Java原型模式——让复杂对象的创建不再复杂

原型模式 原型模式介绍 定义: 原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象相同的新对象。 西游记中的孙悟空&#xff0c;拔毛变小猴&#xff0c;孙悟空这种根据自己的形状复制出多个身外化身的技巧&…

Go语言-文件操作

基本介绍 文件是数据源&#xff0c;数据库也是一种特殊的文件。 Go语言中os.File结构体封装了文件的相关操作。 打开和关闭文件 -----打开文件----- file, err : os.Open("D:/111.txt") if err ! nil{fmt.Println("err ", err) }此时file就是一个指针&…

【电力物联网】云–边协同介绍

(꒪ꇴ꒪ )&#xff0c;Hello&#xff0c;我是祐言QAQ我的博客主页&#xff1a;C/C语言&#xff0c;数据结构&#xff0c;Linux基础&#xff0c;ARM开发板&#xff0c;网络编程等领域UP&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff0c;让我们成为一个强大的技术…

《深入解析 C#(第 4 版)》推荐

《深入解析 C#&#xff08;第 4 版&#xff09;》推荐 在 C# 语言不断演进的技术浪潮中&#xff0c;《深入解析 C#&#xff08;第 4 版&#xff09;》犹如一座灯塔&#xff0c;为开发者照亮探索的道路。无论是经验丰富的老程序员&#xff0c;还是初入 C# 领域的新手&#xff0c…

【网络】Linux 内核优化实战 - net.core.netdev_max_backlog

目录 Linux 内核参数 net.core.netdev_max_backlog 详解一、参数概述二、参数功能与作用2.1 核心功能2.2 网络数据包处理流程 三、查看当前参数值3.1 通过 sysctl 命令3.2 直接读取 /proc/sys 文件 四、修改参数值4.1 临时修改&#xff08;立即生效&#xff0c;重启后失效&…

Nuitka 打包Python程序

文章目录 Nuitka 打包Python程序&#x1f680; **一、Nuitka 核心优势**⚙️ **二、环境准备&#xff08;Windows 示例&#xff09;**&#x1f4e6; **三、基础打包命令****单文件脚本打包****带第三方库的项目** &#x1f6e0;️ **四、高级配置选项****示例&#xff1a;完整命…

自动获取文件的内存大小怎么设置?批量获取文件名和内存大小到Excel中的方法

在对重要数据进行备份或迁移操作前&#xff0c;为确保备份全面无遗漏&#xff0c;且合理规划目标存储设备的空间&#xff0c;会将文件名和内存提取到 Excel。比如&#xff0c;某个部门要将旧电脑中的文件迁移到新服务器&#xff0c;提前整理文件信息&#xff0c;能清晰知道所需…

创建型设计模式——单例模式

单例设计模式 什么是创建型设计模式有哪些创建型设计模式 单例设计模式实现方法饿汉式单例懒汉式单例实现方法 CSDN——C单例模式详解 单例设计模式是一种创建型设计模式 什么是创建型设计模式 创建型设计模式&#xff0c;就是通过控制对象的创建方式来解决设计问题。 有哪…