C++左值与右值核心判断方法

 更新时间:2026年06月04日 09:06:22   作者:代码中介商  
C++ 11 引入右值引用和移动语义后,左值右值的区分变得更加重要,本文介绍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 语言遗留)
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 前)/ 将亡值(特殊情况)

四、哪些是右值

类别示例说明
数字字面量423.14不能取地址
布尔字面量truefalse不能取地址
算术结果a + bx * 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++左值与右值内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 原创的C语言控制台小游戏

    原创的C语言控制台小游戏

    本文给大家分享的是个人原创设计的一个C语言控制台小游戏,非常的简单,但是挺好玩的,推荐给大家,有需要的小伙伴也可以自由扩展下。
    2015-03-03
  • C++数据结构之实现邻接表与邻接矩阵的相互转换

    C++数据结构之实现邻接表与邻接矩阵的相互转换

    这篇文章主要为大家学习介绍了C++如何实现邻接表与邻接矩阵的相互转换,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-07-07
  • C++ 名称空间详情

    C++ 名称空间详情

    当一个项目变得大型之后,我们会引入很多的库,这么一来两个库很可能会定义List、Tree、Node同名的类,编译器要是不考虑这情况的话,程序员调用时就会出现冲突问题。C++提供了名称空间工具,以更好的控制名称的作用域,本文就来谈谈C++ 名称空间,需要的朋友可以参考一下
    2021-09-09
  • C/C++ Qt 数据库与Chart历史数据展示

    C/C++ Qt 数据库与Chart历史数据展示

    这篇文章主要介绍了Qt利用Qchart组件展示数据库中的历史数据。文中的示例代码讲解清晰,具有一定的学习和工作价值,感兴趣的小伙伴可以学习一下
    2021-12-12
  • C++ 标准库中的 <algorithm> 头文件算法操作总结

    C++ 标准库中的 <algorithm> 头文件算法操作总结

    C++ 标准库中的 <algorithm> 头文件提供了大量有用的算法,主要用于操作容器(如 vector, list, array 等),这些算法通常通过迭代器来操作容器元素,本文给大家介绍C++ 标准库中的 <algorithm> 头文件算法总结,感兴趣的朋友一起看看吧
    2025-04-04
  • Qt读写ini文件的方法详解(含源码+注释)

    Qt读写ini文件的方法详解(含源码+注释)

    .ini文件是Initialization File的缩写,即初始化文件,下面这篇文章主要给大家介绍了关于Qt读写ini文件(含源码+注释)的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • Qt GUI图形图像开发之Qt表格控件QTableView简单使用方法及QTableView与QTableWidget区别

    Qt GUI图形图像开发之Qt表格控件QTableView简单使用方法及QTableView与QTableWidget区

    这篇文章主要介绍了Qt GUI图形图像开发之Qt表格控件QTableView简单使用方法,需要的朋友可以参考下
    2020-03-03
  • 基于C++17实现的手写线程池

    基于C++17实现的手写线程池

    本文主要介绍了基于C++17实现的手写线程池,自己实现了Any类,Semaphore类以及Result类的开发,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-08-08
  • C语言详细分析讲解关键字goto与void的作用

    C语言详细分析讲解关键字goto与void的作用

    我们在C语言中经常会见到void,也会偶尔见到goto,那么C语言中既然有goto,为什么我们在代码中见的很少呢?在以前很多的项目经验中,我们得到这样一条潜规则:一般项目都是禁用goto的,程序质量与goto的出现次数成反比。自后也就造成了我们一般不会使用goto
    2022-04-04
  • C++实现LeetCode(129.求根到叶节点数字之和)

    C++实现LeetCode(129.求根到叶节点数字之和)

    这篇文章主要介绍了C++实现LeetCode(129.求根到叶节点数字之和),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07

最新评论