左值和右值¶
左值(value) - 左值指的是表达式结束后依然有“内存地址”且可以被取地址的对象 - 通常表示具名变量或持久存在的对象,能出现在赋值语句的左边 - 简单来说,左值就是可寻址的对象
右值(rvalue)
- 右值是不能取地址的临时值或者纯值表达式
- 代表的是将要消亡的对象或字面量,不具备持久存储地址,通常只能出现在赋值语句右边
- 右值是临时的,不可寻址的
C++ 1 2 3 4 5 6 7
int x = 10; // x 是左值 int* p = &x; // 可以取地址,说明x是左值, p也是左值,&x是右值 int y = x + 5; // x+5 是右值,不能取地址 // int* q = &(x + 5); // 错误,不能取右值地址 int& ref = x; // ref 是左值引用,引用左值
特征 | 左值 | 右值 |
---|---|---|
是否有地址 | 有地址 | 没有地址 |
是否可以取地址 | 可以 | 不能 |
是否可以赋值 | 可以(通常在赋值左边) | 不能(赋值右边) |
典型例子 | 变量、函数返回的左值引用 | 字面量、表达式临时结果 |
1. 基础升级¶
C++ | |
---|---|
1 2 3 |
|
解释:
(a = b)
返回的是a
的引用,所以它是左值,可以继续赋值。- 这种特性在一些链式赋值中有用,比如
x = y = z;
2. 自增自减的陷阱¶
C++ | |
---|---|
1 2 3 4 5 6 7 |
|
解释:
++x
是前置自增,直接修改原变量,返回的仍然是变量本身(左值)。x++
是后置自增,返回的是修改前的临时值(右值)。
3. 函数返回值的差异¶
C++ | |
---|---|
1 2 3 4 5 6 7 |
|
解释:
- 返回左值引用的函数调用是左值。
- 返回普通值的函数调用是右值(除非是
std::move
强制转换)。
4. std::move
和右值引用¶
C++ | |
---|---|
1 2 3 4 5 |
|
解释:
std::move
不会真的移动,它只是把一个左值标记成右值(static_cast<T&&>
)。- 这样构造/赋值运算符就会选择右值引用版本,从而进行移动语义。
拷贝构造
- 会为新对象 s2 分配一块新的内存,把 s1 的内容逐字节复制过去。
内存分配 + 数据复制,代价相对大。
移动构造
- 会直接把 s1 内部的指针 data_ “偷”过来,s1 自身的指针被置为空(或指向空串),所以不需要复制数据,也不需要额外分配内存。只是几个指针和数值的赋值操作,开销极小。
5. 容器元素访问的细节¶
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
解释:
- 容器的下标运算符
operator[]
返回元素的引用,所以是左值。 - 迭代器解引用
*it
返回元素引用,也是左值。
6. 左值引用&右值引用¶
6.1 & 与 && 在类型上的含义¶
在 C++ 中,&
和 &&
出现在类型声明里时,是引用类型修饰符:
符号 | 名称 | 代表的意思 |
---|---|---|
& |
左值引用(lvalue reference) | 绑定到可取地址的对象(左值) |
&& |
右值引用(rvalue reference) | 绑定到临时对象或将亡值(右值) |
例子
C++ | |
---|---|
1 2 3 4 5 6 |
|
6.2 在函数参数上的特殊规则(引用折叠)¶
如果用模板写一个函数,比如:
C++ | |
---|---|
1 2 |
|
这里的 T&&
并不一定是右值引用!
它是万能引用(universal reference)或转发引用(forwarding reference)。
规则:
- 如果传的是左值,
T
会推导成左值引用类型,T&&
会折叠成T&
(左值引用)。 - 如果传的是右值,
T
会推导成普通类型,T&&
保持右值引用。
引用折叠规则:
Text Only | |
---|---|
1 2 3 4 |
|
例子:
C++ | |
---|---|
1 2 3 4 5 6 7 8 |
|
6.3 完美转发(Perfect Forwarding)¶
完美转发的核心思想: 保持实参的值类别(左值/右值)原样传递给另一个函数,避免不必要的拷贝或移动。
通常配合:
- 万能引用参数(
T&&
) std::forward<T>
保留原本的值类别
例子:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
为什么要 std::forward
而不是直接传 arg
?
- 如果直接
process(arg)
,arg
是个命名变量,永远是左值,即使它原来是右值。 std::forward<T>(arg)
会在 T 是非引用类型时恢复右值特性。