文章

C++ 左值与右值 左值引用与右值引用

C++ 左值与右值 左值引用与右值引用

左值与右值

1
2
3
int a = 10; // a是左值, 10是右值
int *p = &a; // *p是左值, &a是右值
*p = 20; 

左值: 表示某个特定内存位置的表达式

  • 可以被赋值(可以出现在赋值号的左边和右边)
  • 有明确的内存地址
  • 例如: int a = 10; 中的a是左值

右值: 不能被赋值的表达式

  • 不能出现在赋值号的左边
  • 临时变量
  • 例如: int a = 10; 中的10是右值

++i: i是左值, 返回的是自增之后的变量本身, 可以被赋值 i++: i是右值, 返回的是自增之前的变量, 不能被赋值

左值引用与右值引用

1
2
int a = 10;
int& b = a;

左值引用: int& b = a;

  • b是左值引用, a是左值
  • 这里引用的a需要在之前已经定义过, 且a是左值, 即之前有: int a = 10;
1
int&& c = 10;

右值引用: int&& a = 10;

  • 这里引用的10是右值, 可以是临时变量
  • 取右值引用时, 会先用一个临时变量保存右值, 然后将这个临时变量的地址赋给右值引用

右值本来是没有地址的, 但是编译器会通过临时变量保存右值, 可以取到这个临时变量的地址, 然后将这个地址赋给右值引用

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int Handle(int& l) {} // 接受左值引用
int Handle2(int&& r) {} // 接受右值引用

int main(){
    int a = 10;
    Handle(a); // 调用Handle函数, 传入的是左值
    Handle(10); // 编译错误, 传入的是右值, 但是Handle函数需要的是左值
    Handle2(10); // 调用Handle2函数, 传入的是右值
    Handle2(a); // 编译错误, 传入的是左值, 但是Handle2函数需要的是右值
}

左值引用与右值引用的意义

右值有一个特殊的性质, 就是基本上只会被使用一次, 对于这种只会被使用一次的右值, 如果是比较大的数据(例如一个很大的数组), 如果用左值引用, 那么会拷贝一份数据, 但是如果用右值引用, 那么只会拷贝一个地址, 节省了内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <vector>

// 函数重载分别接受左值引用和右值引用
void Process(const std::vector<int>& v) {
    std::cout << "Left value reference\n";
}
void Process(std::vector<int>&& v) {
    std::cout << "Right value reference\n";
    // 在这里可以直接操作 v 的资源,而不拷贝
}

int main() {
    std::vector<int> v = {1, 2, 3};
    Process(v);             // 调用左值版本
    Process(std::move(v));  // 调用右值版本
}

使用 std::move 将 v 转化为右值, 从而调用右值引用版本的函数, 这样就可以直接操作 v 的资源, 而不拷贝

本文由作者按照 CC BY 4.0 进行授权