C++11以后常见左值右值判断

张开发
2026/4/21 15:04:03 15 分钟阅读

分享文章

C++11以后常见左值右值判断
左值右值判断题大集合先记一个最粗暴的判断法能不能稳定地表示一块可命名、可定位的对象身份通常更像左值。更像一个临时结果、纯值、将要消失的结果通常更像右值。不过 C11 之后右值又细分成纯右值prvalue将亡值xvalue1普通变量int a 10; a答案左值解析a是一个有名字的对象能取地址能持续存在到作用域结束所以它是左值。2常量变量const int b 20; b答案左值解析很多人容易错。b虽然不能修改但它依然是有名字的对象所以仍然是左值。能不能改和是不是左值不是一回事。3字面量10答案右值纯右值解析字面量就是一个值不代表一个可持续定位的对象身份通常就是纯右值。4算术表达式结果a b答案右值纯右值解析a b计算出来的是一个临时结果不是一个具名对象所以是右值。5赋值表达式左边a 30; (a)答案a是左值解析赋值号左边要求必须是一个可修改左值。这也是左值最经典的特征之一能出现在赋值号左边。6赋值表达式整体(a 30)答案左值解析这是一个很经典的坑。在 C 里内置赋值表达式的结果是左值表示赋值后的那个对象本身。所以可以链式写(a b) c;只要类型允许这是合法的因为(a b)本身是左值。7前置 a答案左值解析前置a返回的是对象自身所以是左值。例如a 100;对于内置类型这居然是合法的因为a是左值。8后置 a答案右值纯右值解析后置a返回的是修改前的旧值副本是一个临时值所以是右值。因此a 100; // 错9前置 ----a答案左值解析和前置一样返回对象自身。10后置 --a--答案右值解析和后置一样返回旧值临时副本。11解引用int* p a; *p答案左值解析*p表示p指向的那个对象本身所以是左值。所以可以*p 999;这也是指针能修改所指对象的根本原因。12取地址表达式a答案右值纯右值解析a产生的是一个临时的地址值不是对象本身所以它是右值。13数组名int arr[5]; arr答案左值解析数组本身是对象数组名在表达式里常常会退化成指针但数组名本体仍然是左值。比如arr是合法的因为arr有对象身份。不过arr something; // 不行因为数组不可赋值不代表它不是左值。14字符串字面量hello答案左值解析这个很多人第一次看会懵。字符串字面量的类型是数组类型编译期生成在 .rodata区 的字符数组例如const char[6]数组是对象因此字符串字面量是左值。不过它通常会在很多场景退化成const char*。15函数返回普通值int f() { return 1; } f()答案右值纯右值解析函数按值返回返回的是一个临时结果所以是右值。16函数返回左值引用int x 10; int f() { return x; } f()答案左值解析返回类型是int说明返回的是某个已有对象的别名所以表达式f()是左值。于是f() 100;合法。17函数返回 const 左值引用const int g() { return x; } g()答案左值解析const int仍然是左值引用所以表达式是左值。只是它是不可修改的左值。18函数返回右值引用int h() { return 1; } // 先只看表达式类别 h()答案右值将亡值 xvalue解析返回类型是int表达式h()是一个将亡值。它代表“绑定到某个资源可被移动的对象”。这就是右值引用表达式常见的类别xvalue。19std::move(a)std::move(a)答案右值将亡值 xvalue解析std::move本质上是把a强制转换成右值引用。它不移动任何东西只是把表达式变成一个可被移动的将亡值。所以a本身还是左值std::move(a)是 xvalue20右值引用变量本身int rr 10; rr答案左值解析这也是超级高频坑。变量只要有名字表达式里用它时通常就是左值。虽然rr的类型是int但表达式rr本身是左值。所以要再次把它当右值用得std::move(rr)21成员访问对象是左值struct S { int m; }; S s; s.m答案左值解析ss是左值对象它的成员s.m也是左值。22成员访问临时对象S{}.m答案右值 / 更准确地说常按临时对象成员理解解析这里要小心标准细节。S{}是一个临时对象prvalue。访问其成员时结果通常不再是普通持久左值语义而是跟临时对象生命周期绑定的结果。面试和学习阶段你可以先记“临时对象的成员访问整体按右值语义理解更接近将亡值/临时结果。”23下标访问arr[0]答案左值解析数组元素本身就是对象arr[0]表示该元素所以是左值。同理*(p 1)也是左值。24内置类型强转结果(int)a答案右值解析这类转换通常会产生一个临时值所以是右值。25static_cast(a)static_castint(a)答案右值解析同理产生一个转换后的临时结果。26static_castint(a)static_castint(a)答案右值将亡值 xvalue解析这和std::move(a)非常像本质就是把a转成右值引用表达式。27条件运算符两边都是左值true ? a : b答案左值前提a和b都是同类型左值解析这是个很容易错的点。如果?:两边都是同类型左值那么整个条件表达式也是左值。所以(true ? a : b) 100;可能是合法的。28条件运算符一边是左值一边是右值true ? a : 10答案右值解析因为两边类别不统一最后通常退成值结果不再保持左值属性。29内置逗号表达式(a, b)答案看最后一个表达式类别如果b是左值那整体是左值。如果最后一个是右值那整体是右值。例如(a, b) // b 是变量 左值 (a, 10) // 10 是右值 右值30new 表达式new int(5)答案右值解析new int(5)返回的是一个指针值这个值本身是临时结果所以是右值。但它指向的* (new int(5))对应的对象是左值。31解引用 new 的结果*(new int(5))答案左值解析虽然new int(5)本身是右值指针但*后得到的是所指向对象所以是左值。当然这会泄漏不建议真这么写。32sizeof(a)sizeof(a)答案右值解析sizeof(a)结果是一个编译期常量值或值结果本质按右值看。33typeid(a)typeid(a)答案左值解析typeid返回的是const std::type_info也就是引用所以表达式是左值。不过这个比较偏不是面试里最核心的左值右值题。34lambda 表达式对象[](){}答案右值纯右值解析lambda 表达式会产生一个闭包对象临时值所以通常按右值看。35具名 lambda 对象auto f [](){}; f答案左值解析f是有名字的对象所以是左值。36模板里万能引用参数名templateclass T void foo(T x) { x; }答案x是左值解析无论x的类型推导出来是啥只要在函数体里写的是具名变量x它就是左值。这也是为什么完美转发要写std::forwardT(x)而不能直接传x。37返回局部变量按值返回int f() { int t 10; return t; }调用f()答案右值通常按纯右值理解解析学习和面试场景下不要把它简单等同成“左值”它不是。可以把它理解成返回了一个值结果可以被移动/复制初始化别的对象。38返回std::move(local)std::string f() { std::string s abc; return std::move(s); }调用f()答案右值解析函数最终按值返回调用表达式仍然是右值。但return std::move(s)往往还会影响优化不一定比直接return s;更好。39字符串对象相加std::string s1 a, s2 b; s1 s2答案右值解析运算结果是一个新的临时std::string对象所以是右值。40move 后的对象名std::string s abc; std::move(s); s答案std::move(s)右值xvalues左值解析move并不会让变量本体变成右值变量它只是那一次表达式转换。变量s以后只要你写s它还是左值。一组更容易错的快问快答int a 1; const int b 2; int* p a; int rr 3;1.a左值2.b左值3.3右值4.a 3右值5.*p左值6.p右值7.rr左值8.std::move(rr)右值xvalue9.a左值10.a右值最后给你一个判断口诀你现在先用这版够实战了一眼判左值有名字的变量左值解引用*p左值下标arr[i]左值前置a / --a左值返回左值引用的函数调用左值赋值表达式(a b)左值一眼判右值字面量10右值算术表达式ab右值后置a / a--右值按值返回的函数调用右值类型转换结果右值std::move(x)右值xvalue最容易错const变量也是左值int rr里的rr只要写名字rr它就是左值字符串字面量hello常按左值数组看条件运算符a ? b : c要看两边类别不一定是右值

更多文章