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;
}

三、哪些是左值

类别 示例 说明
变量名 xnamevec 最常见
解引用指针 *p 指针指向的对象
数组元素 arr[3] 数组元素有确定位置
成员变量 obj.member 对象成员有地址
返回左值引用的函数 vec.front() 返回的是引用
赋值表达式 (a = b) 赋值返回左值
字符串字面量 "hello" 唯一能取地址的字面量(C 语言遗留)
cpp 复制代码
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)

纯右值 = 纯粹的临时值,没有地址,马上就要消失

cpp 复制代码
42;                   // 纯右值:整数字面量
3.14;                 // 纯右值:浮点字面量
true;                 // 纯右值:布尔字面量
a + b;                // 纯右值:运算产生的临时结果
&a;                   // 纯右值:取地址产生的临时指针
[](int x){return x;}; // 纯右值:Lambda 表达式

三、将亡值 (xvalue)

将亡值 = 即将被移动、资源将要被转移的表达式。C++11 新增,主要用于移动语义。

cpp 复制代码
#include <utility>

int x = 10;
std::move(x);          // 将亡值:把 x 转成右值

string getString() {
    return "hello";
}
getString();           // 纯右值(C++17 前)/ 将亡值(特殊情况)

四、哪些是右值

类别 示例 说明
数字字面量 423.14 不能取地址
布尔字面量 truefalse 不能取地址
算术结果 a + bx * y 临时结果
取地址结果 &a 临时指针
Lambda []{} 匿名函数对象
std::move(x) std::move(x) 强制转右值
返回非引用的函数 getValue() 临时对象

第四部分:核心判断法则

最简单的记忆方式

判断 结果
有名字的变量 左值
能放到赋值号左边 左值
不能取地址的临时东西 右值
std::move(x) 的结果 右值(将亡值)

第五部分:特殊情况的判断

一、字符串字面量

cpp 复制代码
"hello";   // 左值!C 语言遗留,字符串字面量是 const char[6]
&"hello";  // ✅ 可以取地址!

42;        // 右值,普通数字字面量
&42;       // ❌ 错误!不能取地址

字符串字面量是唯一的左值字面量

二、赋值表达式

cpp 复制代码
int a, b;
(a = b) = 3;     // 合法!赋值表达式返回左值
a = b = c = 0;    // 链式赋值,就是因为赋值返回左值

// C++ 中 =
// 1. 把右边的值赋给左边
// 2. 整个表达式返回左边的引用(左值)

三、前置自增 vs 后置自增

cpp 复制代码
int x = 10;

++x;       // 返回 x 的引用 → 左值
x++;       // 返回 x 的旧值(临时)→ 右值

++++x;     // ✅ 合法(++x 是左值,可以再 ++)
x++++;     // ❌ 错误(x++ 是右值,不能再 ++)

四、条件表达式

cpp 复制代码
int a = 1, b = 2;
(a > b ? a : b) = 3;  // ✅ 两个都是左值 → 结果是左值

int x = 1;
(x > 0 ? x : 0) = 3;  // ❌ 一个左值一个右值 → 结果是右值

五、成员访问

cpp 复制代码
struct Point { int x, y; };

Point p = {1, 2};
p.x;              // 左值(p 是左值)

Point{3, 4}.x;    // C++11 后可以是左值(临时对象的成员)

第六部分:左值引用

一、基本规则

左值引用 T& 只能绑定到左值

cpp 复制代码
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++ 早期为了效率(避免拷贝)引入的特例。

cpp 复制代码
const int& ref1 = 10;     // ✅ 合法!绑定右值,生命周期延长
const int& ref2 = x + 1;  // ✅ 合法!

int x = 10;
const int& ref3 = x;      // ✅ 也可以绑定左值

const T& 为什么能绑定右值? 编译器会在幕后创建一个临时变量,把右值存进去,然后让引用指向它。这个临时变量的生命周期会延长到引用的生命周期。

cpp 复制代码
// 编译器大概这样处理:
const int& ref = 42;
// ↓ 等价于
// const int __temp = 42;
// const int& ref = __temp;

第七部分:类型与值类别的独立性

一个容易混淆的点:类型和值类别是独立的

cpp 复制代码
int&& rref = 10;  // rref 的类型是 int&&(右值引用类型)
                  // 但 rref 本身是一个有名字的变量
                  // 所以 rref 是左值!

int x = 10;
int&& rref2 = std::move(x);  // rref2 的类型是 int&&
                               // 但 rref2 是左值

核心原则有名字的就是左值,不管它是什么类型int&& 类型的变量本身也是左值。

cpp 复制代码
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&& 变量本身是左值。

相关推荐
JAVA9652 小时前
JAVA面试-并发篇 05-并发包AQS队列实现原理是什么
java·开发语言·面试
玖玥拾2 小时前
C/C++ 基础笔记(七)
c语言·c++
Halo_tjn2 小时前
反射与设计模式1
java·开发语言·算法
珊瑚里的鱼3 小时前
手撕单例模式中的饿汉模式和懒汉模式,懒汉模式还要再多加一个C++11版本的
开发语言·c++·单例模式
_不会dp不改名_3 小时前
python-opencv环境搭建
开发语言·python·opencv
HappyAcmen3 小时前
9.复盘API全套流程
开发语言·python
zh路西法3 小时前
【Linux 串口通信】基于 C++ 多线程的同步/异步串口实现
linux·运维·c++·python
charlie1145141913 小时前
通用GUI编程技术——图形渲染实战(四十五)——D3D12资源与堆管理:从上传到驻留
开发语言·3d·图形渲染·win32
不会C语言的男孩3 小时前
C++ Primer 第12章:动态内存
开发语言·c++