左值、右值、纯右值、将亡值:值类别入门
你有没有写过std::move(x)之后发现它还能被读取或者重载了f(T)和f(T)却搞不清到底调了哪个这些怪现象的根都在「值类别value category」上。C11 起给每个表达式贴了一张隐形标签它直接决定一个表达式能不能取地址、能不能被移动、以及重载决议时该选哪个版本。这篇把五个概念一次讲清并用真跑出来的输出告诉你每种实参选中了哪个函数。官方文档Value categories (cppreference)同一个名字两种命运先看一个会让人困惑的现象// demo.cpp — 编译: g -stdc17 -Wall -O2 demo.cpp -o demo#includeiostream#includestring#includeutilityvoidwho(conststd::string){std::cout走了 const T\n;}voidwho(std::string){std::cout走了 T\n;}voidwho(std::string){std::cout走了 T\n;}intmain(){std::string s{喵};who(s);// 传变量who(std::move(s));// 传「被移动」的 sreturn0;}走了 T 走了 T同一个s直接传和用std::move包一下调的就不是同一个函数。差别不在变量本身而在「这个实参表达式的值类别」变了。下面拆开这张标签。五个概念与一张分类图C11 之后表达式的值类别value category只有三种基本类型再归成两个集合左值lvalue有名字、可取地址的对象。比如变量名s、解引用*p、i的返回值。纯右值prvaluepure rvalue纯粹的「值」没有身份、不能被取地址。比如字面量42、临时对象std::string{t}、表达式a b的结果。将亡值xvalueeXpiring value本来是个左值但明确「我马上要被搬空了」比如std::move(x)的结果、static_castT(x)的结果。它既是泛左值有身份又是右值可移动两个集合里都有它。两个集合是这么划的泛左值 glvalue lvalue xvalue有身份、可被寻址语义上存在的对象右值 rvalue prvalue xvalue可以被移动走C 表达式 (expression) ┌───────────────────┴───────────────────┐ glvalue rvalue (泛左值有身份) (右值可被移动) ┌────────────┐ ┌────────────┐ lvalue xvalue xvalue prvalue (左值) (将亡值) (将亡值) (纯右值) 变量名/ std::move(x) 将亡值 42 / 临时对象 *p / i static_castT(x) 同时属于 a b └──── xvalue 同时落在两边 ────┘ 字面量官方文档reference collapsing 与值类别的关系 (Core Guidelines)怎么判断一个表达式是左值还是右值记住两条够用的经验法则有名字的变量本身是左值即使它的类型是右值引用T见下一篇。能不能取地址能取地址的通常是左值取不了地址的42、std::move(x)、临时对象是右值。下面这张表把三者对上号特征左值 lvalue纯右值 prvalue将亡值 xvalue可取地址能不能是右值不能是右值有身份identity有无有但仍属右值典型来源具名变量、*p、前置字面量、临时对象、abstd::move(x)、static_castT(x)注意 xvalue 是「有身份但不可取地址」的特殊存在它身份上指向某个具体对象但作为右值表达式std::move(x)是编译不过的。值类别如何决定重载决议核心这是理解移动语义最关键的一步。f(T)/f(const T)/f(T)三个重载谁被选中完全由实参的值类别决定。让每个重载打自己的标签传四种不同值类别的实参看真跑的是谁// demo.cpp — 编译: g -stdc17 -Wall -O2 demo.cpp -o demo#includeiostream#includestring#includeutilityvoidclassify(conststd::string){std::cout[const T] 只读/转发引用\n;}voidclassify(std::string){std::cout[T] 左值引用\n;}voidclassify(std::string){std::cout[T] 右值引用\n;}intmain(){std::string name{喵喵};conststd::string cname{只读};classify(name);// 左值 - Tclassify(cname);// const 左值 - const Tclassify(std::move(name));// 将亡值(xvalue) - Tclassify(std::string{临时});// 纯右值(prvalue) - Treturn0;}[T] 左值引用 [const T] 只读/转发引用 [T] 右值引用 [T] 右值引用规律很清楚左值有名字的普通变量只能绑T或const T优先选非 const 的T。const 左值绑不上T落到const T。右值prvalue 或 xvalue优先绑T只有在没有T重载时才会退化去绑const T。这就是为什么std::move(x)之后调的是T版本——std::move把左值「抬」成了 xvalue于是移动语义的重载被选中。值类别就是重载决议的裁判。值类别决定「拷贝」还是「移动」光看选中哪个重载还不够得看它的实际后果。往std::vector里塞对象时左值走拷贝构造、右值走移动构造。给Blob加上构造/拷贝/移动的日志一眼看清// demo.cpp — 编译: g -stdc17 -Wall -O2 demo.cpp -o demo#includeiostream#includestring#includeutility#includevectorstructBlob{std::string name;explicitBlob(std::string n):name{std::move(n)}{std::cout构造 name\n;}Blob(constBlobo):name{o.name}{std::cout拷贝 name\n;}Blob(Blobo)noexcept:name{std::move(o.name)}{std::cout移动 name\n;}};intmain(){std::vectorBlobv;v.reserve(4);Blob a{左值};v.push_back(a);// 左值 - 拷贝构造v.push_back(std::move(a));// 将亡值 - 移动构造v.push_back(Blob{临时});// 纯右值 - 移动构造return0;}构造 左值 拷贝 左值 移动 左值 构造 临时 移动 临时对照着看a作为左值push_back触发「拷贝」对象被复制一份std::move(a)把它变成将亡值后触发「移动」资源被搬走而不是复制Blob{临时}是纯右值临时对象同样走「移动」。值类别不同编译器生成的代码就不同这正是移动语义能提速的根本原因。看这类日志别只数行数真正要看的是「拷贝」和「移动」这两个词。易错点那些「看起来是右值」其实是左值值类别最容易翻车的地方是「凭直觉猜」。把常见表达式的真实值类别列成一张表比反复背定义管用表达式值类别说明xx的类型是T左值有名字就是左值类型是右值引用也改变不了这点std::move(x)将亡值只是static_castT(x)本身不搬移任何东西abc左值字符串字面量是const char[4]数组数组是左值42/3.14纯右值字面量没有身份i左值返回自增后的对象本身i纯右值返回自增前的旧值副本*p左值解引用拿到的是所指的那个对象x纯右值取地址得到的是一个临时地址值a b纯右值运算产生的是临时对象f()f返回T纯右值C17 起返回值是 prvalue配合强制复制消除f()f返回T左值返回引用时保持被引用对象的值类别x.mm是普通成员与x同类别std::move(x).m是将亡值前两条合起来解释了一个经典困惑为什么std::move(x)之后x还能被读取很多人以为move会当场把东西搬走其实它连一个对象字段都没碰。它只做了一次类型转换x那个对象一个字节都没动真正「搬空」它的是接收方——实参被T重载绑定后接收方内部才动手把资源偷走。记住这个分工std::move就不再有神秘感了。还有个更隐蔽的坑形参的类型是右值引用时它在函数体里是左值。看下面这个程序// demo.cpp — 编译: g -stdc17 -Wall -O2 demo.cpp -o demo#includeiostream#includestring#includeutilityvoidsink(std::string){std::coutsink(T) 走拷贝路径\n;}voidsink(std::string){std::coutsink(T) 走移动路径\n;}voidrelay(std::strings){// 形参 s 的类型是右值引用sink(s);// 但 s 有名字 - 它本身是左值 - 拷贝路径sink(std::move(s));// 显式转回将亡值 - 才走移动路径}intmain(){relay(std::string{临时对象});}sink(T) 走拷贝路径 sink(T) 走移动路径s明明是以右值引用接进来的sink(s)却仍然选了拷贝版本。因为s在relay的函数体里是个有名字的变量值类别是左值。这条规则不是缺陷而是刻意的保护如果「名字是右值引用的变量」自动算右值那么第二次使用s时它可能已经被搬空了编译器无从判断程序会静默出错。想把「可移动」这个信息继续往下传必须显式写std::move(s)或std::forward这正是转发forwarding话题的起点。与移动语义的衔接下一步是 std::forward把这篇的结论串起来移动语义的整条链路就通了值类别是裁判它决定f(T)/f(const T)/f(T)三者谁被选中。std::move(x)是把左值抬成将亡值的类型转换本身不移动任何东西。真正的「搬移」发生在被选中的那个函数体内——比如std::vector的移动构造去偷那块缓冲。于是「什么时候该写std::move」有了明确答案当你确定这个对象之后不再需要、且想交给一个会搬走它的接口时。反过来对一个马上还要继续读的变量写std::move等于把资源提前送人后续读取落在「有效但未指定」的状态上属于自伤。泛型代码里std::move就不够用了模板参数T可能被推导成左值引用也可能是普通类型需要的是「原样保留调用者传进来的值类别」。那是std::forwardT(x)的职责它配合转发引用forwarding reference和引用折叠reference collapsing使用。这一块留到《左值引用右值引用与万能引用》展开这里只需记住结论std::move是无条件转成右值将亡值std::forward是有条件地按原值类别转发把「有名字的变量是左值」这一条钉死两者都不会用错。延伸阅读Value categories (cppreference)最权威的分类定义与边界情形。std::move (cppreference)看清move只是「无条件转成右值引用」不做任何搬移动作。std::forward (cppreference)按原值类别转发的实现与边界。C Core Guidelines · F 章参数与重载值类别在接口设计里的实战约束。收个尾值类别就是 C 给每个表达式贴的隐形标签左值有名字、可取地址右值可被移动将亡值是「马上要被搬空」的那个特殊右值。重载决议选哪个版本、走拷贝还是走移动全看这张标签。先把「有名字的变量就是左值」这一条钉死剩下几个概念基本不会用错。下一篇换引用的视角把值类别和T/T对上。

相关新闻

CubeFS blobstore-cli 命令行工具使用指南:配置、命令与源码解析

CubeFS blobstore-cli 命令行工具使用指南:配置、命令与源码解析

存储分布式文件系统对象存储云原生 【免费下载链接】cubefs cloud-native distributed storage 项目地址: https://gitcode.com/gh_mirrors/cu/cubefs 点击查看 免费下载 本篇指南围绕 CubeFS 纠删码(blobstore)子系统的运维调试命令行工具 …

2026/10/5 6:52:26 阅读更多 →
M9A 新手完全指南:环境准备、安装部署与图形界面配置实战

M9A 新手完全指南:环境准备、安装部署与图形界面配置实战

GUI 自动化AI 应用 【免费下载链接】M9A 重返未来:1999 小助手 | Assistant For Reverse: 1999 项目地址: https://gitcode.com/gh_mirrors/m9/M9A 点击查看 免费下载 导读 本文是《重返未来:1999》自动化助手 M9A(Assistant Fo…

2026/10/5 6:52:26 阅读更多 →
【ArkUI进阶练中学】第19课:AI安全与隐私治理

【ArkUI进阶练中学】第19课:AI安全与隐私治理

本节目标 理解 HarmonyOS 7 星盾安全架构从“权限管控”到“意图执行”的范式跃迁,掌握“数据最小化、意图分级仲裁、行为沙箱监控”三重安全机制掌握端侧 AI 模型安全防护的核心手段,包括模型加密、数字签名、完整性校验以及 ModelGuard API 的使用掌握…

2026/10/5 6:51:26 阅读更多 →

最新新闻

Java零基础入门:周末大总结

Java零基础入门:周末大总结

import java.util.Scanner; public class SummaryTry { public static void main(String[] args) { 综合编程题 11:简易学生成绩管理系统(控制台版)】 结合本周所有知识点,完成以下需求: 使用 Scanner 输入3名学生的姓…

2026/10/5 9:23:55 阅读更多 →
AI辅助芯片选型:从痛点拆解到实战工作流

AI辅助芯片选型:从痛点拆解到实战工作流

芯片选型的AI工具,现在其实是个“看着热闹、用着别扭”的领域。真干过硬件的人都知道,上午还在为选一颗合适的LDO翻三个分销商网站,下午就可能因为某颗MCU的交期变成52周而推翻整版方案。最近AI工具的声量很大,但能正经回答“帮我…

2026/10/5 9:23:55 阅读更多 →
自注意力+对抗生成网络:深度子空间聚类实战解析

自注意力+对抗生成网络:深度子空间聚类实战解析

简介:一份关于基于自注意力对抗的深度子空间聚类研究的Word文档,面向机器学习、计算机视觉等领域的研究者和学习者,系统阐述如何融合自注意力机制与对抗网络来提升高维数据聚类的鲁棒性。文档从聚类基础与经典算法讲起,涵盖稀疏子…

2026/10/5 9:23:55 阅读更多 →
分数阶时滞神经网络稳定性分析:Caputo导数、LMI判据与数值验证

分数阶时滞神经网络稳定性分析:Caputo导数、LMI判据与数值验证

简介:这份PDF文献《含有离散时滞及分布时滞分数阶神经网络的渐近稳定性分析》面向从事神经网络、深度学习与数据建模方向的研究生、教师及工程技术人员,聚焦分数阶神经网络在同时含离散时滞与分布时滞情形下的渐近稳定性难题。文章在Caputo导数意义下构造…

2026/10/5 9:23:55 阅读更多 →
从爬虫到可视化:豆瓣读书Top250全栈数据项目实战

从爬虫到可视化:豆瓣读书Top250全栈数据项目实战

做这个豆瓣读书 Top250 全栈数据项目,起因其实特别朴素,就是想给简历里添一个能从头讲到尾的实战项目。python爬虫负责采数据,pandas做数据清洗,MySQL落库,Java后端提供接口,前端再用ECharts把图书数据渲染…

2026/10/5 9:23:55 阅读更多 →
Google AX 开源:用声明式 YAML 编排十亿级 AI Agent 任务

Google AX 开源:用声明式 YAML 编排十亿级 AI Agent 任务

1. 从“一天一个开源项目”聊起:为什么 AX 值得单独写一篇做 Agent 开发这两年,我最大的感受就是:写一个能跑的 Agent 不难,难的是让一千个、一万个甚至更多 Agent 稳定地跑起来、跑对、跑完还能查账。单机跑个 ReAct 循环&#x…

2026/10/5 9:22:54 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →