C++ 左值和右值区别 C++值类别体系详细解释【理论】

3次阅读

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

C++ 左值和右值区别 C++值类别体系详细解释【理论】

怎么一眼判断一个表达式是左值还是右值?

最可靠的方法是尝试对它取地址:&e 能通过编译 → e 是左值;编译失败(如 Error: lvalue required as unary '&' operand)→ e 是右值。

  • int x = 42;:变量名 x 是左值;字面量 42 是右值
  • x + 5std::String("tmp")func()(返回非引用)都是右值 —— 它们没有可访问的地址,生命周期止于当前表达式
  • *parr[0]getRef()(返回 int&)是左值 —— 即使没名字,也代表有身份、可寻址的对象
  • 注意陷阱:"hello"字符串字面量,在 C++ 中是左值(类型为 const char[6],可取地址),不是右值 —— 这常被误判

C++11 之后的值类别到底分哪几类?

别再只记“左值/右值”二分法。C++11 引入三元值类别体系,所有表达式必属其一:

  • 左值(lvalue):具名、可取地址、生命周期长(如 xobj.member
  • 纯右值(prvalue):无名、临时、不占持久内存(如 42x+ystd::move(x) 的结果本身)
  • 将逝值(xvalue):有身份但资源可被“挪走”的对象,典型就是右值引用绑定后的结果(如 std::move(x) 表达式本身是 xvalue,不是 prvalue)

前两者合称 glvalue(泛左值),后两者合称 rvalue(右值)。这个分类直接影响函数重载、移动构造、完美转发能否触发 —— 比如 std::vector 的移动构造函数只接受 T&&,而 T&& 只能绑定 xvalue 或 prvalue,不能绑定普通左值。

为什么 std::move(x) 不真的移动,却能让左值变成右值?

std::move 本质是个强制类型转换函数,签名是 template typename std::remove_reference::type&& move(T&& t)。它不执行任何移动操作,只是把左值 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; } 中的 sreturn 时是 xvalue,不是左值 —— 尽管它有名字,但处于“即将离开作用域”的上下文,编译器按 xvalue 处理以启用移动。

text=ZqhQzanResources