C++左值与右值核心判断方法
作者:代码中介商
引言
在 C++ 学习中,"左值"和"右值"是绕不开的核心概念。它们是理解引用、移动语义、完美转发的基础。很多 C++ 程序员写了多年代码,仍然对"为什么这里能绑、那里不能绑"感到困惑——根本原因就是没搞懂左值右值的判断规则。
C++ 11 引入右值引用和移动语义后,左值右值的区分变得更加重要。本文作为系列第一篇,将聚焦最基础的问题:什么是左值?什么是右值?如何判断?

第一部分:表达式的两个属性
C++ 中每个表达式都有两个独立属性:

第二部分:左值 (lvalue)
一、定义
左值 = 有身份、可寻址的表达式。通俗说就是能取地址的、有名字的东西。
二、判断法则
核心法则:能用 & 取地址的表达式就是左值。
int main() {
int x = 10; // x 是左值
int* p = &x; // ✅ 可以对 x 取地址
int y = 20;
int* p2 = &(x + y); // ❌ 错误!x+y 是临时结果,不能取地址
return 0;
}三、哪些是左值
| 类别 | 示例 | 说明 |
|---|---|---|
| 变量名 | x、name、vec | 最常见 |
| 解引用指针 | *p | 指针指向的对象 |
| 数组元素 | arr[3] | 数组元素有确定位置 |
| 成员变量 | obj.member | 对象成员有地址 |
| 返回左值引用的函数 | vec.front() | 返回的是引用 |
| 赋值表达式 | (a = b) | 赋值返回左值 |
| 字符串字面量 | "hello" | 唯一能取地址的字面量(C 语言遗留) |
int x = 10; // x 是左值
int* p = &x; // *p 是左值(可以 &(*p))
int arr[5] = {1,2,3}; // arr[2] 是左值
string s = "hello";
s[0]; // 左值,返回 char&
int a = 1, b = 2;
(a = b) = 3; // 合法!赋值表达式返回左值第三部分:右值 (rvalue)
一、定义
右值 = 临时对象、字面量、不能取地址的表达式。右值又细分为纯右值和将亡值。

二、纯右值 (prvalue)
纯右值 = 纯粹的临时值,没有地址,马上就要消失
42; // 纯右值:整数字面量
3.14; // 纯右值:浮点字面量
true; // 纯右值:布尔字面量
a + b; // 纯右值:运算产生的临时结果
&a; // 纯右值:取地址产生的临时指针
[](int x){return x;}; // 纯右值:Lambda 表达式
三、将亡值 (xvalue)
将亡值 = 即将被移动、资源将要被转移的表达式。C++11 新增,主要用于移动语义。
#include <utility>
int x = 10;
std::move(x); // 将亡值:把 x 转成右值
string getString() {
return "hello";
}
getString(); // 纯右值(C++17 前)/ 将亡值(特殊情况)四、哪些是右值
| 类别 | 示例 | 说明 |
|---|---|---|
| 数字字面量 | 42、3.14 | 不能取地址 |
| 布尔字面量 | true、false | 不能取地址 |
| 算术结果 | a + b、x * y | 临时结果 |
| 取地址结果 | &a | 临时指针 |
| Lambda | []{} | 匿名函数对象 |
std::move(x) | std::move(x) | 强制转右值 |
| 返回非引用的函数 | getValue() | 临时对象 |
第四部分:核心判断法则

最简单的记忆方式:
| 判断 | 结果 |
|---|---|
| 有名字的变量 | 左值 |
| 能放到赋值号左边 | 左值 |
| 不能取地址的临时东西 | 右值 |
std::move(x) 的结果 | 右值(将亡值) |
第五部分:特殊情况的判断
一、字符串字面量
"hello"; // 左值!C 语言遗留,字符串字面量是 const char[6] &"hello"; // ✅ 可以取地址! 42; // 右值,普通数字字面量 &42; // ❌ 错误!不能取地址
字符串字面量是唯一的左值字面量。
二、赋值表达式
int a, b; (a = b) = 3; // 合法!赋值表达式返回左值 a = b = c = 0; // 链式赋值,就是因为赋值返回左值 // C++ 中 = // 1. 把右边的值赋给左边 // 2. 整个表达式返回左边的引用(左值)
三、前置自增 vs 后置自增
int x = 10; ++x; // 返回 x 的引用 → 左值 x++; // 返回 x 的旧值(临时)→ 右值 ++++x; // ✅ 合法(++x 是左值,可以再 ++) x++++; // ❌ 错误(x++ 是右值,不能再 ++)
四、条件表达式
int a = 1, b = 2; (a > b ? a : b) = 3; // ✅ 两个都是左值 → 结果是左值 int x = 1; (x > 0 ? x : 0) = 3; // ❌ 一个左值一个右值 → 结果是右值
五、成员访问
struct Point { int x, y; };
Point p = {1, 2};
p.x; // 左值(p 是左值)
Point{3, 4}.x; // C++11 后可以是左值(临时对象的成员)第六部分:左值引用
一、基本规则
左值引用 T& 只能绑定到左值。
int x = 10; int& ref1 = x; // ✅ x 是左值 int& ref2 = 42; // ❌ 42 是右值 int& ref3 = x + 1; // ❌ x+1 是右值 int* p = &x; int& ref4 = *p; // ✅ *p 是左值
二、const 左值引用(万能引用)
const T& 既可以绑定左值,也可以绑定右值。这是 C++ 早期为了效率(避免拷贝)引入的特例。
const int& ref1 = 10; // ✅ 合法!绑定右值,生命周期延长 const int& ref2 = x + 1; // ✅ 合法! int x = 10; const int& ref3 = x; // ✅ 也可以绑定左值
const T& 为什么能绑定右值? 编译器会在幕后创建一个临时变量,把右值存进去,然后让引用指向它。这个临时变量的生命周期会延长到引用的生命周期。
// 编译器大概这样处理: const int& ref = 42; // ↓ 等价于 // const int __temp = 42; // const int& ref = __temp;
第七部分:类型与值类别的独立性
一个容易混淆的点:类型和值类别是独立的。
int&& rref = 10; // rref 的类型是 int&&(右值引用类型)
// 但 rref 本身是一个有名字的变量
// 所以 rref 是左值!
int x = 10;
int&& rref2 = std::move(x); // rref2 的类型是 int&&
// 但 rref2 是左值核心原则:有名字的就是左值,不管它是什么类型。int&& 类型的变量本身也是左值。
void foo(int& x) { cout << "左值引用" << endl; }
void foo(int&& x) { cout << "右值引用" << endl; }
int main() {
int&& rref = 10; // rref 是左值!
foo(rref); // 调用 foo(int&) — 输出"左值引用"
foo(std::move(rref)); // 调用 foo(int&&) — 输出"右值引用"
}总结
一、核心判断法则

二、引用绑定规则
| 引用类型 | 可以绑定 |
|---|---|
T& | 只能左值 |
const T& | 左值 + 右值(万能) |
T&& | 只能右值 |
三、一句话记忆
左值是有身份、可寻址、持久存在的表达式(有名字的变量、解引用指针),右值是临时对象和字面量(不能取地址)。const T& 是万能引用能绑一切,T& 只能绑左值,T&& 只能绑右值。有名字的 int&& 变量本身是左值。
到此这篇关于C++左值与右值:核心判断法则详解的文章就介绍到这了,更多相关C++左值与右值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
