c++中的引用
在c++中,为了改变c语言中指针使用的种种麻烦与困难,定义了一种新的复合类型---引用,引用也分为很多种,比如左值引用、右值引用、常量引用等等。
左值引用
一般情况下,大家见的最多的就是左值引用。右值引用通常与移动操作结合起来的比较多。其中左值引用使用&
符号,右值引用使用&&
。
下面是左值引用的两个大类。 ## 普通引用
引用为对象起了另外一个名字,引用类型引用(refers
to)另外一种类型。通过将声明符写成&d
的形式来定义引用类型,其中d是声明的变量名。如下:
1 |
|
如下是几个注意问题: 1. 引用必须初始化!因为引用是无法赋值的,对引用赋值其实是对引用绑定的变量赋值。
普通引用(非常量引用)是不能绑定常量的!因为可以使用引用对绑定的变量进行赋值,所以如果引用绑定了常量,就会有改变常量的操作发生的可能。
没有引用的引用,但是可以通过引用绑定到一个对象(如
int i=1; int &a=i; int &b=a;
这样是可以的,但是b绑定的是i)。因为引用本身只是一个别名,不是一个对象,所以不能定义引用的引用。引用的类型必须与绑定的对象严格匹配。
引用只能绑定在对象上,不能与字面值或者某个表达式的计算结果绑定。
拓展:定义内置数组的引用。
因为内置数组不可拷贝,所以内置数组的引用就会十分有用,并且高效。使用方法如下:
1
2
3int arr[10] = {1}; //第一个元素为1,其他元素默认初始化为0
int (&arrRef)[10] = arr; //其实就是多取了一个名
cout<<arrRef[0]; //输出1
常量引用
科普一下,const可以分为顶层const与底层const两种。一般对象只会有顶层const,表示对象本身是常量不能修改;而对于指针与引用变量除了顶层const外(表示自身是常量,一般只对指针而言,引用一般只关心底层const),还有底层const,表示自身指向或者引用的对象是常量。
此处常量引用指的是底层const,即不能通过此引用改变绑定的对象。常量引用有几个特殊的地方:
常量引用可以绑定常量,也可以绑定非常量(但是不能通过此引用改变这个非常量的值);
常量引用的类型不必与绑定的对象严格一致,只要可以转换成常量引用的类型即可;
常量引用可与字面值或者某个表达式的计 算结果绑定;
其中第一条很容易理解,第二条、第三条的原因可由下面例子知道原因:
1
2
3
4
5double d1 = 3.14;
const int &r1 = d1; //正确!
//上式等价于如下:
const int temp = d1; //先生成一个临时变量(与常量引用类型一致)
const int &r1 = temp; //让r1绑定这个临时量
右值引用
新标准中为了支持移动操作,有了右值引用的概念。顾名思义,右值引用就是绑定到右值的引用。
1
2
3
4
5
6
int i = 42;
int &r = i; //正确:r是变量i的引用
int &&rr = i; //错误:不能将一个右值引用绑定到一个左值上
int &r2 = i*42; //错误:不能将一个普通引用绑定到一个右值上
const int &r3 = i*42; //正确:可以讲一个常量引用绑定到一个临时变量上
int &&rr2 = i*42; //正确:将右值引用绑定到一个右值上
1 |
|
由上我们可以看出,左值一般有持久的状态,是一个稳定的对象;而右值要么是字面值常量,要么是表达式求值过程中创建的临时变量。
此处需要说明的一点是变量表达式都是左值,我们不能将一个右值引用绑定到一个右值引用上,因为右值引用本身是左值,是可以通过右值引用改变它指向对象的值的。如下:
1
2
3int &&r1=42; //正确:42是一个右值
int &&r2=r1; //错误:右值引用类型的变量表达式r1是一个左值
//注意:这样的操作在左值引用就是可以成立的。
std::move()
提到右值引用,就不得不提std::move()
这个函数(头文件utility
)。这个函数的作用就是将参数包装成一个右值。
因为右值引用为左值,所以我们可以通过右值引用改变源对象的值:
1
2
3
4
5
6int i = 1;
//int &&rr1 = i; 错误,不能绑定左值
int &&rr2 = std::move(i); //此处move返回一个右值类型的值被绑定到右值引用类型变量rr2
rr2 = 2; //正确;通过右值引用改变变量i的值
cout<<i<<endl; //输出2
//int &&rr3 = rr2; 错误,rr2是左值
通常肯定不会这样使用move,通常我们将move函数来辅助完成一个对象的移动操作,因此,往往调用
move
意味着就承诺:除了对源对象重新赋值或者销毁它外,我们将不再使用它。在对一个源对象做了move操作后,我们只能保证移后源对象结构依旧完好,可以正常销毁,但是它的值我们不能做任何假设。
万能引用
其实还有一种引用,那就是万能引用,万能引用形式和右值引用一样。万能引用只出现在型別推导的场景下,万能引用可以根据不同的语境选择是成为左值或者右值引用。
std::forward()
万能引用总是会搭配std::forward()
使用,这个函数会根据语境选择是否将参数包装成一个右值。仅在实参原本是右值时才会。因此这中操作也叫做完美转发。
forward
和move
其实就是强制类型转换,只不过前者是有条件的转换,后者无条件转换。
参考资料
- [1] C++ Primer(第5版)