引用 
  
引用可以看成是 C++ 封装的指针,用来传递它所指向的对象。在 C++ 代码中实际上会经常和引用打交道,但是通常不会显式地表现出来。引用的基本原则是在声明时必须指向对象,以及对引用的一切操作都相当于对原对象操作。另外,引用不是对象,因此不存在引用的数组、无法获取引用的指针,也不存在引用的引用。
注意引用类型不属于对象类型,所以才需要 reference_wrapper  这种设施。
 
引用主要分为两种,左值引用和右值引用。此外还有两种特殊的引用:转发引用和垂悬引用,不作详细介绍。另外,本文还牵涉到一部分常值的内容,请用 常值  一文辅助阅读。
左值引用 
左值和右值 
如果你不知道什么是左值和右值,可以参考 值类别  页面。
 
左值表达式 
如果一个表达式返回的是左值,那么这个表达式被称为左值表达式。右值表达式亦然。
 
通常我们会接触到的引用为左值引用,即绑定到左值的引用,但 const 的左值引用可以绑定到右值。以下是来自 参考手册  的一段示例代码。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 #include   <iostream> 
#include   <string> 
int   main ()   { 
   std :: string   s   =   "Ex" ; 
   std :: string &   r1   =   s ; 
   const   std :: string &   r2   =   s ; 
   r1   +=   "ample" ;    // 修改 r1,即修改了 s 
   // r2 += "!";               // 错误:不能通过到 const 的引用修改 
   std :: cout   <<   r2   <<   '\n' ;    // 打印 r2,访问了s,输出 "Example" 
} 
 
左值引用最常用的地方是函数参数,通过左值引用传参可以起到与通过指针传参相同的效果。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 
15 #include   <iostream> 
#include   <string> 
// 参数中的 s 是引用,在调用函数时不会发生拷贝 
char &   char_number ( std :: string &   s ,   std :: size_t   n )   { 
   s   +=   s ;            // 's' 与 main() 的 'str' 是同一对象 
                    // 此处还说明左值也是可以放在等号右侧的 
   return   s . at ( n );    // string::at() 返回 char 的引用 
} 
int   main ()   { 
   std :: string   str   =   "Test" ; 
   char_number ( str ,   1 )   =   'a' ;    // 函数返回是左值,可被赋值 
   std :: cout   <<   str   <<   '\n' ;     // 此处输出 "TastTest" 
} 
 
右值引用 (C++ 11) 
右值引用是绑定到右值的引用。右值 可以在内存里也可以在 CPU 寄存器中 。另外,右值引用可以被看作一种 延长临时对象生存期的方式 。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 
13 
14 #include   <iostream> 
#include   <string> 
int   main ()   { 
   std :: string   s1   =   "Test" ; 
   // std::string&& r1 = s1;           // 错误:不能绑定到左值 
   const   std :: string &   r2   =   s1   +   s1 ;    // 可行:到常值的左值引用延长生存期 
   // r2 += "Test";                    // 错误:不能通过到常值的引用修改 
   std :: string &&   r3   =   s1   +   s1 ;    // 可行:右值引用延长生存期 
   r3   +=   "Test" ;    // 可行:能通过到非常值的右值引用修改 
   std :: cout   <<   r3   <<   '\n' ; 
} 
 
在上述代码中,r3 是一个右值引用,引用的是右值 s1 + s1。r2 是一个左值引用,可以发现 右值引用可以转为 const 修饰的左值引用 。
一些例子 
++i 和 i++ 
++i 和 i++ 是典型的左值和右值。++i 的实现是直接给 i 变量加一,然后返回 i 本身。因为 i 是内存中的变量,因此可以是左值。实际上前自增的函数签名是 T& T::operator++();。而 i++ 则不一样,它的实现是用临时变量存下 i,然后再对 i 加一,返回的是临时变量,因此是右值。后自增的函数签名是 T T::operator++(int);。
int   n1   =   1 ; 
int   n2   =   ++ n1 ; 
int   n3   =   ++   ++ n1 ;    // 因为是左值,所以可以继续操作 
int   n4   =   n1 ++ ; 
// int n5 = n1++ ++;   // 错误,无法操作右值 
// int n6 = n1 + ++n1; // 未定义行为 
int &&   n7   =   n1 ++ ;    // 利用右值引用延长生命期 
int   n8   =   n7 ++ ;      // n8 = 5 
 
移动语义和 std::move(C++11) 
在 C++11 之后,C++ 利用右值引用新增了移动语义的支持,用来避免对象在堆空间的复制(但是无法避免栈空间复制),STL 容器对该特性有完整支持。具体特性有 移动构造函数 、移动赋值  和具有移动能力的函数(参数里含有右值引用)。
另外,std::move 函数可以用来产生右值引用,需要包含 <utility> 头文件。
注意:一个对象被移动后不应对其进行任何操作,无论是修改还是访问 。被移动的对象处于有效但未指定的状态,具体内容依赖于 stl 的实现。如果需要访问(即指定一种状态),可以使用该对象的 swap 成员函数或者偏特化的 std::swap 交换两个对象(同样可以避免堆空间的复制)。
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
10 
11 
12 // 移动构造函数 
std :: vector < int >   v { 1 ,   2 ,   3 ,   4 ,   5 }; 
std :: vector < int >   v2 ( std :: move ( v ));    // 移动v到v2, 不发生拷贝 
// 移动赋值函数 
std :: vector < int >   v3 ; 
v3   =   std :: move ( v2 ); 
// 有移动能力的函数 
std :: string   s   =   "def" ; 
std :: vector < std :: string >   numbers ; 
numbers . push_back ( std :: move ( s )); 
 
注意上述代码仅在 C++11 之后可用。
函数返回引用 
让函数返回引用值可以避免函数在返回时对返回值进行拷贝,如
char   & get_val ( std :: string   & str ,   int   index )   {   return   str [ index ];   } 
 
你不能返回在函数中的局部变量的引用,如果一定要在函数内的变量。请使用动态内存。例如如下两个函数都会产生悬垂引用,导致未定义行为。
std :: vector < int >&   getLVector ()   {    // 错误:返回局部变量的左值引用 
   std :: vector < int >   x { 1 }; 
   return   x ; 
} 
std :: vector < int >&&   getRVector ()   {    // 错误:返回局部变量的右值引用 
   std :: vector < int >   x { 1 }; 
   return   std :: move ( x ); 
} 
 
当右值引用指向的空间在进入函数前已经分配时,右值引用可以避免返回值拷贝。
struct   Beta   { 
   Beta_ab   ab ; 
   Beta_ab   const &   getAB ()   const &   {   return   ab ;   } 
   Beta_ab &&   getAB ()   &&   {   return   std :: move ( ab );   } 
}; 
Beta_ab   ab   =   Beta (). getAB ();    // 这里是移动语义,而非拷贝 
 
参考内容 
C++ 语言文档——引用声明  
C++ 语言文档——值类别  
Is returning by rvalue reference more efficient?  
浅谈值类别及其历史  
 
  
 
 
 
   本页面最近更新: ,更新历史  
   发现错误?想一起完善? 在 GitHub 上编辑此页!  
   本页面贡献者:OI-wiki  
   本页面的全部内容在 CC BY-SA 4.0  和 SATA   协议之条款下提供,附加条款亦可能应用