声明具名变量为引用,即既存对象或函数的别名。

  1. 左值引用声明符:声明 S& D; 是将 D 声明为 声明说明符序列 S 所确定的类型的左值引用。
  2. 右值引用声明符:声明 S&& D; 是将 D 声明为 声明说明符序列 S 所确定的类型的右值引用。

引用必须被初始化为指代一个有效的对象或函数:见引用初始化。

不存在 void 的引用,也不存在引用的引用。

引用不是对象;它们不必占用存储,尽管编译器会在需要实现所需语义(例如,引用类型的非静态数据成员通常会增加类的大小,量为存储内存地址所需)的情况下分配存储。

因为引用不是对象,所以不存在引用的数组,不存在指向引用的指针,不存在引用的引用:

1
2
3
int& a[3]; // 错误
int&* p; // 错误
int& &r; // 错误

引用折叠

通过模板或 typedef 中的类型操作可以构成引用的引用,此时适用引用折叠(reference collapsing)规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用:

1
2
3
4
5
6
7
8
typedef int&  lref;
typedef int&& rref;
int n;

lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&

(这条规则,和将 T&& 用于函数模板时的模板实参推导的特殊规则一起,组成了使得 std::forward 可行的规则。)

左值引用

左值引用可用于建立既存对象的别名(可拥有不同的 cv 限定):

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

int main()
{
std::string s = "Ex";
std::string& r1 = s;
const std::string& r2 = s;

r1 += "ample"; // 修改 s
// r2 += "!"; // 错误:不能通过到 const 的引用修改
std::cout << r2 << '\n'; // 打印 s,它现在保有 "Example"
}

它们也能用于在函数调用中实现按引用传递语义:

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

void double_string(std::string& s)
{
s += s; // 's' 与 main() 的 'str' 是同一对象
}

int main()
{
std::string str = "Test";
double_string(str);
std::cout << str << '\n';
}

当函数的返回值是左值引用时,函数调用表达式变成左值表达式:

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

char& char_number(std::string& s, std::size_t n)
{
return s.at(n); // string::at() 返回 char 的引用
}

int main()
{
std::string str = "Test";
char_number(str, 1) = 'a'; // 函数调用是左值,可被赋值
std::cout << str << '\n';
}

右值引用

右值引用可用于为临时对象延长生存期(注意,到 const 的左值引用也能延长临时对象生存期,但这些对象无法因此被修改):

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

int main()
{
std::string s1 = "Test";
// std::string&& r1 = s1; // 错误:不能绑定到左值

const std::string& r2 = s1 + s1; // OK:到 const 的左值引用延长生存期
// r2 += "Test"; // 错误:不能通过到 const 的引用修改

std::string&& r3 = s1 + s1; // OK:右值引用延长生存期
r3 += "Test"; // OK:能通过到非 const 的引用修改
std::cout << r3 << '\n';
}