C++左值右值
C++ 左值右值
- 左值(lvalue):有名字、有地址的对象,能出现在赋值号左边
- 右值(rvalue):没有名字、临时存在的值,只能出现在赋值号右边
如何判断
| 能否被赋值? | 能否取地址? | 类型 |
|---|---|---|
| 能 | 能 | 左值 |
| 不能 | 不能(大部分情况) | 右值 |
int x = 42;
&x; // 左值能取地址
&(x + 1); // 编译错误,右值不能取地址
常见示例
int a = 10; // a 是左值(有名字,可以 &a 取地址)
// 10 是右值(字面量常量,不能取地址)
int b = a + 5; // b 是左值(变量,有名字,可以取地址)
// a + 5 是右值(表达式结果的临时值)
// ------------------- 一些容易混淆的情况 -------------------
int c = (a); // (a) 仍然是左值(加括号不影响)
int d = a++; // a++ 返回右值(自增前的临时值)
// a 本身依旧是左值
int e = ++a; // ++a 返回左值(自增后的 a,本身可以继续取地址)
int f = a * 2; // a * 2 是右值(临时结果)
// f 是左值(变量)
int& foo(); // 假设 foo 返回 int& (引用)
int bar(); // 假设 bar 返回 int (值)
int& r = foo(); // foo() 返回左值(引用,可取地址)
int x = bar(); // bar() 返回右值(纯值,临时对象)
| 示例 | 类型 | 说明 |
|---|---|---|
变量 x |
左值 | 有地址、可以赋值 |
字面量 10 |
右值 | 没地址、不能取地址 |
x + 1 |
右值 | 运算结果是一个临时值 |
"abc" |
右值 | 字符串字面量是临时值 |
| 函数返回值(返回非引用) | 右值 | |
| 函数返回引用 | 左值 |
与引用的关系
| 引用类型 | 能绑定右值 | 能绑定左值 | 用途 |
|---|---|---|---|
T& |
不能 | 能 | 修改已有变量 |
const T& |
能 | 能 | 安全地读取,不可修改 |
T&& |
能 | 不能 | 专门操作右值,进行移动 |
int a = 10;
int& r1 = a; // 左值引用绑定左值
int& r2 = 10; // 错误,不能绑定右值
const int& r3 = 10; // 常量引用可以绑定右值
int&& r4 = 10; // 右值引用绑定右值
右值引用
语法是 T&&,只允许绑定右值(不能绑定有名字的变量)。右值引用让你可以“接住”临时对象的资源,然后偷走它们,而不是复制。
移动语义
std::string a = "hello";
std::string b = std::move(a); // move 后,a 的资源转移给 b
std::string s1 = "abc";
std::string s2 = s1; // 拷贝构造(复制内容)
std::string s3 = std::move(s1); // 移动构造(直接窃取 s1 的资源)
移动构造函数/赋值运算符
class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// 把 other 的资源“偷”过来
}
MyClass& operator=(MyClass&& other) noexcept {
// 先释放自己资源,再接管 other's
return *this;
}
};
强烈推荐给移动构造函数加上 noexcept,因为标准库容器(如 std::vector、std::string) 在内部做扩容时:
- 优先使用移动构造函数(比拷贝效率高)
- 但前提是:这个移动构造是
noexcept的
否则就退回使用拷贝构造,导致性能变差。
引用限定符
引用限定符(Ref-qualifiers)是 C++11 引入的一种语法,用于区分成员函数对对象引用类型的限定,常见于成员函数声明后面,表示该成员函数只能被左值对象或右值对象调用。
主要用来控制成员函数的调用权限,尤其对重载成员函数行为和资源管理(比如移动语义)非常重要。
- 左值引用限定符
&:表示该成员函数只能被左值对象调用。 - 右值引用限定符
&&:表示该成员函数只能被右值对象调用。 - 无引用限定符:该成员函数可以被左值和右值调用。
struct Foo {
void foo() & { std::cout << "called on lvalue\n"; }
void foo() && { std::cout << "called on rvalue\n"; }
};
int main() {
Foo f;
f.foo(); // 调用左值版本
Foo().foo(); // 调用右值版本
}
输出:
called on lvalue
called on rvalue
用途详解
-
区分对左值和右值的操作
允许对左值对象和右值对象提供不同的行为,尤其对支持移动语义的类非常重要。
-
当操作临时对象(右值)时,可以安全地“偷走”资源(移动构造或移动赋值)。
-
当操作具名对象(左值)时,通常不希望破坏原对象。
-
-
防止错误的调用
比如写一个函数只想在临时对象上调用,或者只允许在持久对象上调用,引用限定符可以做到。
-
配合移动语义
比如
std::string中的operator+,返回的临时字符串可以调用带&&限定符的成员函数,避免不必要的复制。
结合 const 使用
引用限定符可以和const一起用:
void foo() const &; // const左值对象调用
void foo() const &&; // const右值对象调用
| 函数签名 | 调用对象类型限制 | 备注 |
|---|---|---|
void f() |
左值和右值均可 | 默认 |
void f() & |
只能左值调用 | 用于修改左值的成员函数 |
void f() && |
只能右值调用 | 用于移动语义或优化 |
void f() const & |
只能常量左值调用 | 不修改对象的左值 |
void f() const && |
只能常量右值调用 | 不修改对象的右值 |
-
只用于非静态成员函数声明末尾,表示该成员函数只能被特定类型的对象调用。
-
如果一个成员函数写了引用限定符(比如
&或&&),那么所有该函数同名、参数列表完全相同的重载版本,也都建议写上引用限定符。struct A { void foo() & { std::cout << "lvalue\n"; } // void foo() && { std::cout << "rvalue\n"; } // 如果这里没写 foo() &&,对右值调用 foo() 会找不到匹配版本 // 要么都写上引用限定符,覆盖所有情况;要么都不写,保持传统行为 };引用限定符是函数签名的一部分,
foo() &和foo()是两种不同的成员函数签名。

浙公网安备 33010602011771号