声明具名变量为引用,即既存对象或函数的别名。
语法
引用变量声明是声明符拥有下列形式的简单声明
& attr(可选) 声明符 | (1) | |
&& attr(可选) 声明符 | (2) | (C++11 起) |
1) 左值引用声明符:声明 S& D; 将 D
声明为到 声明说明符序列 所确定的类型 S
的左值引用。
2) 右值引用声明符:声明 S&& D; 将 D
声明为到 声明说明符序列 所确定的类型 S
的右值引用。
声明符 | - | 除引用声明符之外的任何其他声明符(不存在引用的引用) |
attr(C++11) | - | 可选的属性列表 |
引用必须被初始化为指代一个有效的对象或函数:见引用初始化。
不存在 void 的引用,也不存在引用的引用。
引用类型无法在顶层被 cv 限定;声明中没有为此而设的语法,并且通过 typedef、decltype 或模板类型实参所引入的限定性是被忽略的。
引用不是对象;它们不必占用存储,尽管若需要分配存储以实现所需语义(例如,引用类型的非静态数据成员通常会增加类的大小,量为存储内存地址所需),则编译器会这么做。
因为引用不是对象,故不存在引用的数组,不存在指向引用的指针,不存在引用的引用:
- int& a[3]; // 错误
- int&* p; // 错误
- int& &r; // 错误
##### 引用坍缩 容许通过模板或 typedef 中的类型操作构成引用的引用,这种情况下适用引用坍缩(reference coolapsing)规则:右值引用的右值引用坍缩成右值引用,所有其他组合均坍缩成左值引用:
(这条规则,和将 T&& 用于函数模板时的模板实参推导的特殊规则一起,组成了使得 std::forward 可行的规则。) | (C++11 起) |
左值引用
左值引用可用于建立既存对象的别名(可选地拥有不同的 cv 限定):
运行此代码
- #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"
- }
它们亦可用于在函数调用中实现按引用传递语义:
运行此代码
- #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';
- }
当函数的返回值是左值引用时,函数调用表达式成为左值表达式:
运行此代码
- #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';
- }
#### 右值引用 右值引用可用于为临时对象延长生存期(注意,左值引用亦能延长临时对象生存期,但不能通过左值引用修改它们): 运行此代码
更重要的是,当函数同时具有右值引用和左值引用的重载时,右值引用重载绑定到右值(包含纯右值和亡值),而左值引用重载绑定到左值: 运行此代码
这允许在适当时机自动选择移动构造函数、移动赋值运算符和其他具移动能力的函数(例如 std::vector::push_back())。 因为右值引用能绑定到亡值,故它们能指代非临时对象:
这使得可以将作用域中不再需要的对象移动出去:
| (C++11 起) |
#### 转发引用 转发引用是一种特殊的引用,它保持函数实参的值类别,使得能利用 std::forward 转发实参。转发引用是下列之一: 1) 函数模板的函数形参,其被声明为同一函数模板的类型模板形参的无 cv 限定的右值引用:
2) auto&&,但当其从花括号包围的初始化器列表推导时则不是:
参阅模板实参推导和 std::forward。 | (C++11 起) |
悬垂引用
尽管引用一旦初始化,就始终指代一个有效的对象或函数,但有可能创建一个程序,被指代对象的生存期结束,但引用仍保持可访问(悬垂(dangling))。访问这种引用是未定义行为。一个常见例子是返回自动变量的引用的函数:
- std::string& f()
- {
- std::string s = "Example";
- return s; // 退出 s 的作用域:
- // 调用其析构函数并解分配其存储
- }
- std::string& r = f(); // 悬垂引用
- std::cout << r; // 未定义行为:从悬垂引用读取
- std::string s = f(); // 未定义行为:从悬垂引用复制初始化
注意,右值引用和到 const 的左值引用能延长临时对象的生存期(参阅引用初始化中的规则和例外情况)。
若被指代对象被销毁(例如通过显式的析构函数调用),但存储尚未被解分配,则到生存期外的对象的引用仍能以有限的方式使用,且当在同一存储中重新创建对象时也可以变为有效(细节见在生存期之外进行访问)。