引用与左右值
1. 引用
引用(Reference) 是一个已存在变量的别名,它提供了一种更方便的方式来间接访问变量,语法为类型 &别名 = 变量名;:
int num = 10;
int& ref = num; // ref 是变量 num 的别名
1.1 引用的注意事项
1. 引用必须在定义的时候初始化。
int& ref; // 错误操作
2. 引用不能再绑定其它变量。
int num1 = 10, num2 = 20;
int& ref = num1;
ref = num2; // 这个操作是把 num2 的值赋给 num1,而不是让ref成为num2的引用
1.2 常引用
常引用(Const Reference) 是指向常量的引用,使用const关键字修饰,语法为const 类型 &常引用名 = 变量名/常量名。
int num = 10;
const int& const_ref = num;
- 即使常引用指向的是变量,也不允许通过引用对其进行修改。
2. 左值与右值
2.1 左值
左值(lvalue):具有持久内存地址的表达式;一般来说,变量、数组元素、对象成员都是左值:
int num = 10; // num 是左值
int* ptr = # // ptr 是左值
2.2 右值
右值(rvalue):没有持久内存地址的临时表达式,它们通常是字面量、临时对象或者函数的返回值。
rvalue的r是right的意思,因为在赋值表达式中右值只能出现在赋值号的右边。
int result = 10 + 20; // 10、20、10+20的中间结果都是右值
2.3 左值与右值的区分
1. 寻址层面:
左值具有内存地址,可以通过取地址符&来获取其地址,而右值不可以:
int num = 10;
int* ptr1 = # // 可以获取左值的地址
int* ptr2 = &10; // 不可以获取右值的地址
字符串字面量是一个左值,可以取地址。
其地址在.rodata段:
auto* p = &"Hello world!";
2. 临时对象层面
函数的形参是左值:
void func(int x)
{
int *ptr = &x;
}
func(10);
10本身是右值,但是在当参数传入func后的x是左值,相当于有一步int x = 10;
函数返回会产生临时对象,临时对象是右值:
int x = 1;
int get_x()
{
return x;
}
x本身是左值,但是int get_x()函数返回的是x的临时副本,是一个右值。
注: 1) 现代编译器普遍支持返回值优化(RVO)和具名返回值优化(NRVO),直接在接收方的内存空间构造对象,省略拷贝过程。
std::string build_str()
{
std::string s = "Hello world";
return s;
}
// 调用函数,直接在接收方构造对象
std::string hello_str = build_string();
2) 不能返回函数内部局部变量的指针或引用,局部变量会在函数结束后被销毁,进而会产生野指针或悬空引用。
int& bad_return()
{
int a = 10;
return a;
}
3. 左值引用与右值引用
我们前面所讲的单个&的引用都是左值引用,右值引用在 C++11 中引入,使用两个&&表示,语法为类型 &&右值引用名 = 右值表达式;。
int&& rvalue_ref = 10; // rvalue_ref 是一个右值引用
引用后的变量本身是左值,右值引用只是一个类型修饰符,表示它只能绑定到右值。
3.1 非常量左值引用与常量左值引用
1. 非常量的左值引用绑定对象只能是左值
int num = 10;
int& a = num; // 正确,a 是 num 的左值引用
int& b = 10; // 错误,不能将非常量左值引用绑定到右值
2. 常量左值引用允许绑定到右值,同时会延长右值的生命周期
const int& const_ref = 10; // 正确,const_ref 是一个常量左值引用,可以绑定到右值
// 绑定后 const_ref 就像一个正常的左值一样,可以使用它来访问右值的值
// 但是由于常量语义的存在,无法进行修改
3.2 常量右值引用
常量右值引用语法正确,但基本无意义
const int&& const_rvalue_ref = 10; // 正确,但无意义


