最可靠方法是尝试取地址:&e能编译则e为左值,否则为右值;C++11起值类别分为lvalue、prvalue、xvalue三类,分别对应具名可寻址、无名临时、有身份可移资源对象。

怎么一眼判断一个表达式是左值还是右值?
最可靠的方法是尝试对它取地址:&e 能通过编译 → e 是左值;编译失败(如 error: lvalue required as unary '&' operand)→ e 是右值。
-
int x = 42;:变量名x是左值;字面量42是右值 -
x + 5、std::string("tmp")、func()(返回非引用)都是右值 —— 它们没有可访问的地址,生命周期止于当前表达式 -
*p、arr[0]、getRef()(返回int&)是左值 —— 即使没名字,也代表有身份、可寻址的对象 - 注意陷阱:
"hello"是字符串字面量,在 C++ 中是左值(类型为const char[6],可取地址),不是右值 —— 这常被误判
C++11 之后的值类别到底分哪几类?
别再只记“左值/右值”二分法。C++11 引入三元值类别体系,所有表达式必属其一:
-
左值(lvalue):具名、可取地址、生命周期长(如
x、obj.member) -
纯右值(prvalue):无名、临时、不占持久内存(如
42、x+y、std::move(x)的结果本身) -
将逝值(xvalue):有身份但资源可被“挪走”的对象,典型就是右值引用绑定后的结果(如
std::move(x)表达式本身是 xvalue,不是 prvalue)
前两者合称 glvalue(泛左值),后两者合称 rvalue(右值)。这个分类直接影响函数重载、移动构造、完美转发能否触发 —— 比如 std::vector 的移动构造函数只接受 T&&,而 T&& 只能绑定 xvalue 或 prvalue,不能绑定普通左值。
为什么 std::move(x) 不真的移动,却能让左值变成右值?
std::move 本质是个强制类型转换函数,签名是 template。它不执行任何移动操作,只是把左值 x 的类型“标记”为右值引用,从而让后续重载解析选中移动版本。
立即学习“C++免费学习笔记(深入)”;
- 它把左值
x转成 xvalue(不是 prvalue),所以可以绑定到T&& - 但若你对
std::move(x)再次取地址:&std::move(x),仍会编译失败 —— 因为 xvalue 仍不可取地址(符合右值语义) - 常见错误:对已
std::move过的对象继续使用(如std::move(x); x.size();)—— 此时x处于有效但未定义状态,行为由类型决定(如std::vector移动后为空)
函数返回值到底是左值还是右值?看返回类型和调用方式
返回值的值类别不取决于函数体里怎么写,而取决于声明的返回类型 + 是否被命名:
- 返回非引用类型(
std::string func();)→ 返回的是 prvalue(临时对象) - 返回左值引用(
std::string& func();)→ 返回的是左值(绑定到某个已有对象) - 返回右值引用(
std::string&& func();)→ 返回的是 xvalue(资源可被挪用) - 注意 NRVO(命名返回值优化):即使返回局部对象,编译器也可能省略拷贝,但值类别规则不变 —— 优化不改变语义,只改变实现
真正容易混淆的是“具名返回值”:C++17 起,std::string f() { std::string s; return s; } 中的 s 在 return 时是 xvalue,不是左值 —— 尽管它有名字,但处于“即将离开作用域”的上下文,编译器按 xvalue 处理以启用移动。









