Sep 02, 2025
左值和右值详解
C++ 的左值(lvalue)和右值(rvalue)是 C++ 语言中一个非常基础但又极其重要的概念。它们是 C++ 表达式(expression)的两种基本属性,深刻地影响着代码的性能和写法,尤其是在 C++11 标准引入“移动语义”(Move Semantics)之后。
简单来说,区分左值和右值的核心标准是:这个表达式所代表的数据,我们还能不能在后面的代码里再次访问到它。
- 左值 (lvalue - locator value):指那些在内存中有固定地址、可以被取地址(用 - &运算符)并且在表达式结束后依然存在的表达式。你可以把它想象成一个有名字、有固定地址的“容器”。
- 右值 (rvalue - read value):指那些临时的、没有固定地址、在表达式结束后就会被销毁的表达式。你只能读取它的值,但不能对它取地址(通常情况下)。可以把它看作一个“即用即弃”的临时数据。 
从“赋值操作符”的左右看起(经典 C++98 时代)
在 C++ 的早期,最简单的区分方法是看它能否出现在赋值操作符 = 的左边:
- 能放在 - =左边的,就是左值。 因为放在左边意味着你要给它赋值,它必须有一个明确的、持久的存储位置。
- 不能放在 - =左边的,就是右值。 因为它们是临时的,给一个即将销毁的东西赋值是毫无意义的。
示例:
| 1 | int a = 10; // 'a' 是左值,因为它有内存地址,我们可以反复使用它。'10' 是右值,它是一个字面量,没有固定地址。 | 
常见的左值:
- 变量名 ( - a,- b)
- 数组元素 ( - arr[0])
- 返回左值引用的函数调用 ( - get_a_reference())
- 解引用的指针 ( - *p)
常见的右值:
- 字面量 ( - 10,- true,- 'c')
- 算术表达式的结果 ( - a + b)
- 返回非引用类型的函数返回值 ( - get_a_value())
- Lambda 表达式 
C++11 的革命:右值引用的诞生与移动语义
到了 C++11 标准,情况变得更加有趣和重要。为了解决临时对象产生的深拷贝(deep copy)带来的巨大性能开销,C++11 引入了右值引用(Rvalue Reference)和移动语义(Move Semantics)。
三、现代 C++ 的值类别(C++11 及以后)
为了更精确地描述表达式的属性,C++11 以后将值的类别划分得更细,分为五个类别:
- 左值 (lvalue): 传统的左值,有固定身份和位置。 
- 纯右值 (prvalue - pure rvalue): 传统的右值,比如字面量 - 10、- true,以及函数返回的非引用临时对象。它们是“纯粹”的值,不与任何具体对象相关联。
- 将亡值 (xvalue - expiring value): 这是 C++11 新增的概念。它代表那些生命周期即将结束的对象,通常是 - std::move的结果或返回右值引用的函数调用。它虽然像右值一样资源可以被窃取,但它又和某个具体的对象(曾经是左值)相关联。
这五个类别之间有如下关系:
- 广义左值 (glvalue - generalized lvalue) = 左值 + 将亡值。(表示有身份的对象) 
- 右值 (rvalue) = 纯右值 + 将亡值。(表示可以被移动的对象) 
对大多数开发者而言,你不需要每天都去记忆这五个类别的精确定义。你只需要理解核心思想:
- 左值:有名字、能取地址、持久的对象。 
- 右值:临时的、即将销毁的、可以被“移动”的对象。 
- std::move:一个“授权”,允许编译器将一个左值当作右值来处理,以便触发移动语义。
总结
| 特性 | 左值 (lvalue) | 右值 (rvalue) | 
|---|---|---|
| 核心含义 | 持久的对象,有固定内存地址 | 临时的值,表达式结束后即销毁 | 
| 能否取地址 ( &) | 可以 | 通常不可以(将亡值是例外) | 
| 能否在 =左边 | 可以 | 不可以 | 
| 绑定到引用类型 | 左值引用 ( T&) | 右值引用 ( T&&) | 
| 与性能的关系 | 通常涉及拷贝(Copy) | 允许移动(Move),性能更高 | 
| 典型例子 | 变量名、数组元素 | 字面量、表达式结果、 std::move的结果 | 
理解左值和右值的区别是掌握现代 C++ 内存管理和性能优化的关键。它不仅是理论知识,更是编写高效、优雅代码的基石。