C++: 左值(lvalue)与右值(rvalue)详解

【C++】右值引用(极详细版) 右值引用的两个价值;万能引用和完美转发 阅读详情

在 C++ 中,值的类别(value categories) 是语言语义的核心:它决定了表达式能否被赋值、能否绑定到引用、是否是临时对象、是否可以“被移动”。理解左值/右值及其演化(C++11 引入的 xvalue、prvalue、glvalue 等)对写出正确、高效的现代 C++ 非常重要。本文把概念、规则、常见例子、陷阱与最佳实践都讲清楚,方便你在工程中直接使用。

一、先看直观解释(直觉版)

  • 左值(lvalue):表示“有地址、能被命名或能被取地址”的对象。典型是变量、解引用的指针、类成员访问等。你可以对左值取地址、可以赋值给它(a = 5)。

  • 右值(rvalue):通常是临时值、字面量或表达式的中间结果(例如 1, a + b, std::string("s"))。它们通常不能取地址(除非被绑定到引用),是短暂存在的。

  • xvalue(expiring value,过期值):既有对象身份又“即将被搬走”的值(例如 std::move(x) 的结果、返回 T&& 的函数调用)。xvalue 能被移动(move),std::move 生成的正是 xvalue。

  • 在 C++11 以后,值类别细分更细,这是为移动语义和完美转发提供语义基础。

二、正式分类(简要但精确)

C++11 及以后把表达式的类别分为以下几类(常见术语):

  • glvalue(generalized lvalue) = lvalue 或 xvalue

  • prvalue(pure rvalue) = 纯右值(如字面量、返回 by-value 的临时(在 C++17 前会物化为临时))

  • xvalue(expiring value) = 过期值(可视为“可移动的左值”)

日常使用中,你可以理解为:

  • lvalue —— 持久、可命名、有地址;

  • prvalue —— 临时、短暂、没有可取地址的身份(直到被物化);

  • xvalue —— 既有地址(是对象的一部分),又快要被“窃取”/移动。

三、为什么这些区分重要?

这些类别影响:

  • 能否绑定到某种引用:T&、const T&、T&& 的绑定规则不同。

  • 函数重载选择:f(T&) 与 f(T&&) 在重载解析时会根据表达式类别分派不同版本。

  • 移动语义(move constructor / move assignment)什么时候被调用。

  • 完美转发:模板 T&& 参数需要判别实参的值类别以正确转发(std::forward)。

  • 临时对象的生存期、拷贝消除(copy elision / NRVO / guaranteed elision in C++17)等。

四、绑定规则(常用的几条)

  1. 非 const 左值引用 (T&) 只能绑定到左值(不能绑定到临时)。

int x = 1;
int& r = x;      // OK
int& r2 = 1;     // 错误:不能把临时绑定到 T&

2.const 左值引用 (const T&) 可以绑定到左值也可以绑定到临时(并会延长临时的寿命到引用的作用域)。

const int& r = 1; // OK,临时的生命周期延长到 r 的作用域结束

3.右值引用 (T&&) 可以绑定到右值(包括 xvalue、prvalue)。

int&& rr = 1;               // OK
int a = 1;
int&& rr2 = std::move(a);  // OK,std::move(a) 是 xvalue

4.绑定到 reference 的声明(在声明处直接初始化引用)会延长临时对象的生命周期(适用于 const T& 和绑定到右值引用 T&& 的情形),但把临时传给函数参数并不会延长到函数外。

五、典型例子(演示行为)

#include <iostream>
#include <utility>
#include <string>

void f_lref(std::string& s) { std::cout << "lref\n"; }
void f_clref(const std::string& s) { std::cout << "const lref\n"; }
void f_rref(std::string&& s) { std::cout << "rref\n"; }

int main() {
    std::string a = "hello";

    f_lref(a);                 // 匹配 lvalue 版本
    // f_lref("tmp");          // 错误:不能把临时传给 T&

    f_clref("tmp");            // const lvalue ref 可以绑定临时

    f_rref(std::move(a));      // std::move(a) 是 xvalue,调用 rvalue 版本
    // f_rref(a);              // 错误:a 是 lvalue

    // 绑定延长
    const std::string& r = std::string("world"); // 临时延长到 r 的作用域
    std::cout << r << "\n";
}

输出(示意):

lref
const lref
rref
world

六、std::move 与 std::forward 的含义

  • std::move(x):把 x 转换为对应的右值引用类型(T&&),从而允许移动语义。但 std::move 不做实际移动,它只是一个类型转换(cast)。实际是否移动取决于后续调用的函数(例如是否调用移动构造函数)。

  • std::forward<T>(x):在模板中用于完美转发——当你写 template<typename T> void g(T&& t),std::forward<T>(t) 会在 T 是 U& 时作为左值转发,在 T 是 U 时作为右值转发。只有在转发引用(forwarding/universal reference)情形下使用 std::forward。

注意:错误地频繁对对象 std::move 会导致“move 后对象处于未指定但有效状态”,多数情况下后续不可再当作原先的值使用。

七、移动语义(move constructor / move assignment)示例

#include <iostream>
#include <vector>
#include <utility>
#include <string>

struct S {
    std::string data;
    S(const char* s) : data(s) { std::cout << "ctor\n"; }
    S(const S& other) : data(other.data) { std::cout << "copy ctor\n"; }
    S(S&& other) noexcept : data(std::move(other.data)) { std::cout << "move ctor\n"; }
};

int main() {
    S s1("hello");
    S s2 = s1;            // copy ctor
    S s3 = std::move(s1); // move ctor
}

输出可能是:

ctor
copy ctor
move ctor

提示:给类型实现 noexcept 的移动构造会让标准容器(如 std::vector)在重新分配时优先使用移动而不是拷贝(有利于性能)。

八、重载分派(lvalue vs rvalue 函数重载)

你可以重载两个版本,一个接受 T&、一个接受 T&&,以对 lvalue 和 rvalue 做不同处理:

#include <iostream>

void g(int& x) { std::cout << "lvalue overload\n"; }
void g(int&& x) { std::cout << "rvalue overload\n"; }

int main() {
    int a = 0;
    g(a);           // lvalue overload
    g(0);           // rvalue overload
    g(std::move(a)); // rvalue overload
}

这就是实现“按值类别选择不同操作”的基础(例如移动而不是拷贝)。

九、完美转发示例(模板里保留传入值类别)

#include <utility>
#include <iostream>

void do_work(std::string& s) { std::cout << "do_work lvalue\n"; }
void do_work(std::string&& s) { std::cout << "do_work rvalue\n"; }

template <typename F, typename T>
void wrapper(F&& f, T&& param) {
    // 若希望把传入 param 的值类别传给 f,使用 std::forward<T>(param)
    std::forward<F>(f)(std::forward<T>(param));
}

int main() {
    std::string s = "hi";
    wrapper(do_work, s);              // do_work lvalue
    wrapper(do_work, std::string("x"));// do_work rvalue
}

std::forward 的作用是在模板中把值类别“透传”给被调用函数。

十、临时对象与生命周期延长

  • 将临时绑定到 const T&(或在声明处直接绑定到 T&&)可以延长临时对象的生命周期到引用的作用域结束:

const std::string& r = std::string("temp"); // 临时直到 r 退出作用域才销毁
std::string&& rr = std::string("temp2");    // 同样延长到 rr 的作用域

但把临时作为函数参数传递(不是在声明处直接绑定到引用)通常不会延长到函数外;不要以为函数内部的引用会让临时在外部继续存在。

十一、C++17 中关于 prvalue 的语义变化(简述)

C++17 对 prvalue(纯右值)的求值模型做了调整:

  • 保证了在某些情境(如 return T{...};)下的拷贝/移动消除(guaranteed copy elision):编译器不再被允许产生额外的拷贝构造或移动构造——对象直接在调用者的空间构造。

  • prvalue 不再会立即产生一个可寻址的临时对象,而是在需要时“物化(materialize)”对象。这影响了取地址、绑定等边界情形,但在大多数普通代码中你只要知道 C++17 后返回 by-value 更高效即可。

简言之:C++17 让返回临时对象更高效、语义更直观。

十二、常见错误与陷阱(务必记住)

把局部对象的引用返回(悬空引用):

const std::string& bad() {
  std::string s = "oops";
  return s; // 悬空引用,s 已被销毁
}
  1. 误用 std::move:std::move 只是把对象标记为“可移动”,不要对之后还打算使用的对象调用 std::move(它会把其状态置为“可但未指定”)。

  2. 对 const 对象 std::move 没用:std::move(const_obj) 生成 const T&&,而移动构造通常需要 T&&(非 const),因此会退化为拷贝。

  3. 在模板中错误使用 std::forward:std::forward 只能在完美转发模板中按 T 模板参数使用,误用会导致未定义行为或值类别丢失。

  4. 以为移动后对象为空白可任意使用:移动后对象保证是有效但未指定状态(只要满足类型的不变性约束),但不是可以忽略的。

  5. 过度使用右值引用作为函数参数:T&& 在非模板上下文表示“只接受右值”,这会限制函数灵活性。通常为方针函数使用 const T&,为需窃取资源使用 T&&(或提供两种重载版本)。

十三、最佳实践(总结与建议)

  • 输入参数:使用 const T&(或对小型标量直接用 T)。

  • 需要接收任意左/右值并转发:在模板中使用 T&&(转发引用)并用 std::forward<T> 转发。

  • 实现移动语义:为资源类型实现移动构造函数和移动赋值,标注 noexcept(以便容器优先移动)。

  • 避免返回局部引用;对临时要么返回值(C++17 已高效),要么返回智能指针。

  • 使用 std::move 明确意图,但只在你确定不再需要源对象时使用。

  • Prefer std::forward only in forwarding contexts(即模板 T&& 参数处)。

十四、速查表

结束语

左值与右值(以及 xvalue/prvalue/glvalue)是理解 C++ 现代特性的钥匙:移动语义、完美转发、零拷贝返回等都基于这些概念。掌握这些可以帮你写出既安全又高效的代码,并能避免常见的悬空引用、误用 std::move、错误的模板转发等陷阱。

理解C++值类别(lvalue, rvalue, prvalue, xvalue) 本文详细解析C++值类别的演进与应用, 涵盖左值, 右值, 纯右值及将亡值的概念及其在现代C++中的作用. 通过学习右值引用与移动语义, 开发者可以优化资源管理, 减少不必要的拷贝操作, 从而提高程序性能. 适合希望深入了解C++底层机制的开发者阅读. 阅读详情

相关推荐

C++11中的rvalue references(一):左值和右值(lvalue and rvalue)

* “If you’re an experienced C++ programmer and are anything like me, you initially approached C++11 thinking, “Yes, yes, I get it. It’s C++, only more so.” But as you learned more, you were surpri

蓝天和白云的博客 1291

左值与右值

左值与右值 一、左值 左值表示一个占据内存中可识别位置的一个对象,更进一步地,可以对左值取地址 int a = 10; int *p = &a; int **q = &p; a,p,q都是很经典的左值,可以通过标识符a,p,q,取出内存地址中对应的对象 int a;// ① a = 4;// ② ①如果在函数中执行该语句的话,变量a会在栈帧中开辟一个4字节的内存空间其值未定义。所以a为左值,能够取其地址 ②赋值语句中左操作数必须是一个左值,赋值操作本质上是对内存进行更新,所以我们必须要找

weixin_45425093的博客 5733

《机器人学一(Robotics(1))》_台大林沛群 第 3 周 【机械手臂 顺运动学】Quiz 3

的方向,旋转方向与四指弯曲方向相反,为负, α为 -90。的方向,旋转方向与四指弯曲方向相反,为负, α为 -90。bug 的图 要求解的 i 位置有误 两套 题目?B. 第2个 坐标系 的原点位置 不便于 计算;上面的正确,后两题 有误, 不知道问题在哪里。标准部分, 前面的更常用,这种仅作为 了解。还是计算过程中保留两位有效数字 不符合。2、确定 旋转角 α 正负的右手定则。1、确定坐标系方向的右手定则。可相交,距离为 0。A: 右手拇指指向。小数点后两位,不符合。

weixin_46034116的博客 1242

左值和右值的概念

左值和右值的概念

challenglistic的博客 4241

MATLAB实战:5步搞定GNSS单点定位解算(附完整代码)

本文提供了一份基于MATLAB的GNSS单点定位实战指南。通过5个核心步骤,详细讲解了如何从卫星坐标和伪距观测值出发,利用最小二乘迭代算法,构建并实现一个完整的单点定位解算器。文章附有完整代码,并涵盖精度评估与结果分析,适合GNSS初学者和MATLAB用户快速上手实践。

a2b3c4d5e的博客 250

Conda虚拟环境用conda和pip安装软件包的路径问题

解决在虚拟环境中用pip默认把软件包安装到全局site-packages中。

stone9528的博客 4万+

C/C++-左值、右值及引用

目录 1.左值and右值 2.引用 3.左值引用的用途 4.std::move和std::swap C和C++中定义了引用类型(reference type),存在左值引用(lvalue reference)。而在C++11中,新增了右值引用(rvalue refe...

weixin_30634661的博客 668

【低空经济】多源异构数据融合的低空智能感知系统设计与实现

本文探讨了低空智能感知系统在多源异构数据融合中的设计与实现,重点解决无人机等低空飞行器的安全监测问题。通过雷达、光学和红外传感器的数据融合,系统识别准确率提升至92%,并实现120ms的低延迟处理。文章详细介绍了时空对齐、特征级融合和动态权重自适应等关键技术,为低空经济发展提供可靠技术支持。

weixin_29065659的博客 342

左值、右值

C++引入右值引用的核心意义在于性能优化,尤其是通过移动语义减少不必要的拷贝操作,提升资源管理的效率。此外,右值引用支持完美转发,增强了泛型编程的灵活性,并且通过区分左值和右值使得函数重载更加直观和高效。右值引用的引入使得C++在处理复杂对象和高性能应用时变得更加高效和灵活。

LIHONGLI000125的博客 1700

Python爬虫实战:深度解析B站弹幕异步采集技术与数据可视化

title=f"弹幕词云 Top 100 (BV: {bvid})", title_textstyle_opts=opts.TextStyleOpts(font_size=23).add(series_name="弹幕词云", data_pair=list(top_100_words.items()), word_size_range=[20, 100]).add_xaxis([f"{i}-{i+1}分" for i in range(len(time_distribution))])

2201_76125261的博客 640

《网络安全自学教程》- 防火墙原理分析与Windows防火墙使用配置

防火墙分类及原理,Windows开启防火墙,Windows防火墙添加策略,

wangyuxiang946的博客 1万+

C++: 左值(lvalue)与右值(rvalue)详解。

左值通常指可以取地址的、有持久状态的表达式,而右值通常是临时的、即将被销毁的值。左值的典型例子包括变量、函数返回的引用或解引用的指针。右值引用的主要用途是实现移动构造函数和移动赋值运算符,避免不必要的深拷贝。右值引用的主要用途是实现移动构造函数和移动赋值运算符,避免不必要的深拷贝。左值引用是C++中常见的引用类型,通常用于绑定到左值。左值引用是C++中常见的引用类型,通常用于绑定到左值。右值引用专门用于绑定到右值,支持移动语义和完美转发。右值引用专门用于绑定到右值,支持移动语义和完美转发。

MWN_glpq的博客 310

VTK在QT与非QT环境下使用的区别

VTK可以在QT框架内使用,也可以独立于QT使用,这两种使用方式在架构、交互和渲染方面有显著差异。

深之JohnChen的专栏 628

C++ 左值和右值

此处编译器有一个小优化,因为用template的话编译器会自动推导你的类型,例如Int左值传进来,T如果推导为int就会造成结果为int &&,那这是右值引用,传入左值是有问题的。上述的基本类型(int这些)不涉及移动语义,因为移动语义的优势就是减少了大数据的拷贝,只改变指针,不涉及内存深拷贝,所以移动只对拥有复杂数据的数据类型能有显著效果,基本类型不需要。很合理,但是又引发了另一个问题,函数func里面如果存在func2调用,要接收一个右值的param来作为参数,可是param是一个左值,如何处理呢?

m0_55957195的博客 377

DeepSeek Harness 工程实践与高级特性:从测试到安全的完整指南

AI Agent 能执行命令、读写文件、自主决策,能力越强风险越大——删文件、泄密钥、死循环、竞态条件随时可能发生。本文是 DeepSeek Harness 系列第三篇,深入剖析其工程实践与高级特性:从单元测试到真实 API 冒烟测试的分层策略,拒绝 Mock 依赖、坚持"验证世界而非自我报告";从正交结果、规范化契约到 Dispose 静止的防御式编程铁律;以及跨平台多层进程沙箱、文件系统版本守卫、fail-closed 审批机制、模型编写的工作流引擎与上下文压缩等高级能力。

program哲学 207

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

C++的左值(lvalue)和右值(rvalue)

lvalue(左值)、rvalue(右值)这些术语来自C语言(当然,C语言的术语习惯也可能来自更早的语言,Gemfield就不追溯了)。在C语言中,lvalue和rvalue中的l和r是left和right,分别代表着赋值表达式(等号)的左边和右边。并且: 其中等号左边的g1必须是lvalue,g2可以是lvalue或者rvalue。但其实把lvalue中的l看成是location就容易理解,就是lvalue是有直接的memory location的,而rvalue没有。比如: gem是lvalue

南风fahaxiki 2483

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

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

C++ 值的分类:左值和右值

ComputerInBook的专栏 1667

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

深入理解 C++ 中的左值(Lvalue)与右值(Rvalue)

Lvalue 是 “locator value” 的缩写,意为“定位值”。简单来说,左值就是一个表示内存中某个位置的表达式。持久性(Persistent):左值在表达式结束后依然存在。可取地址(Addressable):你可以获取一个左值的内存地址(通过运算符)。可修改(Modifiable):如果左值不是const的,那么它的值可以被修改。常见的左值例子:int x = 10;这里的x是一个左值。return val;// get_value() 是一个左值"hello"

greenspan的专栏 1152

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

C++中的表达式可分为左值(lvalue)和右值(rvalue):左值具有内存地址,可持久存在并修改(如变量名、函数返回的引用);右值则临时短暂,不可修改(如字面量、函数返回值)。C++11引入右值引用和移动语义后,对值类别进行了更精细的划分,新增了纯右值(prvalue)和将亡值(xvalue)。这些概念支撑了移动语义(通过std::move"窃取"资源)和完美转发(通过std::forward保持值类别),显著提升了大型对象处理的效率。理解这些分类是现代C++高效编程的基础。

蜗牛沐雨 1621

lvalue / rvalue and lvalue reference / rvalue reference

表达式的类型 :type of an expression表达式的类别和表达式最终返回的对象、值、函数的类型相同。// 该表达式会在编译的时候确定 两个 int 相除,产生int类型的结果,因此 int 就是该表达式的类型。// 在c++中 auto 并不是一种动态类型,而是在编译时确定的,变量的类型在初始化时已经确定。// 算数运算符必须具有匹配类型的操作数 在计算 12.0 / 4;时,int 类型会被转换为 double 类型,然后进行除法运算。表达式的类型是在编译时确定的。

liu_12345_liu的博客 1112

C++中的左值(Lvalue)和右值(Rvalue)详解

左值(Lvalue):指的是有持久存储的对象。它们的值可以被获取和修改,并且在表达式结束后依然存在。例如,普通的变量就是左值,因为它们占据了内存,并且可以被重新赋值。右值(Rvalue):通常指的是没有持久存储的对象。它们是表达式的临时结果,通常会在表达式结束后被销毁。例如,字面量(如整数常量42)和临时对象(如a + b的结果)就是右值,因为它们只在表达式求值期间存在。左值和右值是C++中非常重要的概念,理解它们对写出高效、现代的C++代码至关重要。左值表示持久存储,右值则表示临时结果。

m0_52796585的博客 1492

【C++】左值(lvalue)​和右值(rvalue)​的理解

在 C++ 中,​左值(lvalue)​和右值(rvalue)​是表达式性质的术语,直接影响对象的生命周期、内存管理及函数重载等行为。

浩瀚之水的专栏 915

C++ 左值与右值(Lvalue/Rvalue)全解析

本文详细解析了C++中左值与右值的核心概念及其应用。左值是有名称、可寻址的持久对象,右值则是临时、无名称的短期对象。C++11引入右值引用(T&&)后,进一步细分为纯右值和将亡值,支持移动语义和完美转发。通过代码示例展示了左值引用与右值引用的区别,重点阐述了移动语义如何避免大对象拷贝,提升性能。文章还分析了函数参数中的值匹配规则,并提供了核心特性对比表,帮助开发者深入理解这一重要概念。

2301_80136889的博客 689

c++中判断左值和右值及应用案例

c++中判断左值和右值及应用案例

qqrrjj2011的博客 1029

左值和右值

左值和右值 我们经常可以看到关于 左值(L-value) 和 右值(R-value) 的概念,那么到底什么是左值,什么是右值,它们之间的区别又是在哪里呢? 左值(lvalue)和右值(rvalue)最先来源于C语言。最先在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值。 比如: int ii = 5; //ii是左值,5是右值 int jj = ii;

mal327的专栏 2898

【C++】左值、右值与移动语义

本文介绍了C++中的左值(lvalue)与右值(rvalue)概念及其应用。左值是有标识符、可寻址的持久对象,右值是临时的、匿名的值。重点讲解了移动语义(Move Semantics)的实现原理,通过右值引用"窃取"临时对象的资源,避免不必要的深拷贝。文章还对比了左值引用与指针的区别,并通过Qt C++示例展示了移动构造函数、std::move的实际应用场景,演示了如何高效处理大型对象和临时对象。这些特性显著提升了C++程序的性能表现。

2301_81559955的博客 1327
上一篇: C++: 深入解析std::bind
下一篇: C++:彻底理解左值和右值(从概念到实践,基础版)
湫兮之风
博客等级 码龄8年 1066粉丝 222原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值