引用

引用常说的是“左值引用”,C++11中新增了一种引用,即“右值引用”。

左值引用

引用为对象起了另外一个名字,引用必须初始化。

1
2
3
int ival = 1024;
int &refVal = ival; // refVal指向ival(是ival的另一个名字)
int &refVal2; // 报错;引用必须初始化

因为,一般在初始化时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。 一但初始值完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另一个对象,因此引用必须初始化。

引用即别名:

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

int main() {
int ival = 1024;
int &refVal = ival;
refVal = 2;
std::cout << "ival = " << ival << std::endl;
std::cout << "refVal = " << refVal << std::endl;
return 0;
}

输出: > ival = 2
> refVal = 2

我们更改引用refVal的值,发现ival的值也发生了改变,这正是因为引用只是原来对象的别名。

引用的定义:

允许一条语句中定义多个引用,其中每个引用标识符都必须以字符&开头。

引用只能绑定在对象上。

1
2
3
int &refVal4 = 10;          // 错误:引用类型的初始值必须是一个对象
double dval = 3.14;
int &refVal5 = dval; // 错误:此处引用类型初始值必须是int型对象

右值引用(rvalue reference)

为了支持移动操作,新标准引入了一种新的引用类型----右值引用

所谓右值引用就是必须绑定到右值的引用,我们通过&&而不是&来获得右值引用。

右值引用只能绑定到一个将要销毁的对象。

一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。

1
2
3
4
5
6
int i = 42;
int &r = i; // 正确:r引用int
int &&rr = i; // 错误:不能将一个右值引用绑定到一个左值上
int &r2 = i * 42; // 错误:i * 42 是一个右值
const int &r3 = i * 42; // 正确:我们可以将一个const的引用绑定到一个右值上
int &&rr2 = i * 42; // 正确:i * 42为一个右值

返回左值的函数,连同赋值,下标,解引用和前置递增/递减运算符,都是返回左值的表达式。 我们可以将一个左值引用绑定到这类表达式的结果上。

返回非引用类型的函数,连同算术,关系,位以及后置递增/递减运算符,都生成右值。 我们可以将一个const的左值引用或一个右值引用绑定到这类表达式上。

左值持久。右值短暂

左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。

变量是左值

变量可以看作只有一个运算对象而没有运算符的表达式。

1
2
int &&rr1 = 42;         // 正确:字面常量是右值
int &&rr2 = rr1; // 错误:表达式rr1是左值!

标准库movw函数

虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准 库函数来获得绑定到左值上的引用。

此函数定义在头文件utility中。

1
2
int &&rr1 = 42;
int &&rr3 = std::move(rr1);

move调用告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。

调用move就意味着承诺:除了对rr1赋值或销毁它外,我们将不再使用它。在调用move之后,我们不能对移后源对象的值做任何假设。