C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
引用声明
声明具名变量为引用,即既存对象或函数的别名。
语法
引用变量声明是声明符拥有下列形式的简单声明
& attr(可选) 声明符
|
(1) | ||||||||
&& attr(可选) 声明符
|
(2) | (C++11 起) | |||||||
D
声明为到 声明说明符序列 所确定的类型 S
的左值引用。D
声明为到 声明说明符序列 所确定的类型 S
的右值引用。声明符 | - | 除引用声明符之外的任何其他声明符(不存在引用的引用) |
attr(C++11) | - | 可选的属性列表 |
引用必须被初始化为指代一个有效的对象或函数:见引用初始化。
不存在 void 的引用,也不存在引用的引用。
引用类型无法在顶层被 cv 限定;声明中没有为此而设的语法,并且通过 typedef、decltype 或模板类型实参所引入的限定性是被忽略的。
引用不是对象;它们不必占用存储,尽管若需要分配存储以实现所需语义(例如,引用类型的非静态数据成员通常会增加类的大小,量为存储内存地址所需),则编译器会这么做。
因为引用不是对象,故不存在引用的数组,不存在指向引用的指针,不存在引用的引用:
int& a[3]; // 错误 int&* p; // 错误 int& &r; // 错误
引用坍缩容许通过模板或 typedef 中的类型操作构成引用的引用,这种情况下适用引用坍缩(reference coolapsing)规则:右值引用的右值引用坍缩成右值引用,所有其他组合均坍缩成左值引用: 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&& (这条规则,和将 |
(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'; }
右值引用右值引用可用于为临时对象延长生存期(注意,左值引用亦能延长临时对象生存期,但不能通过左值引用修改它们): 运行此代码 #include <iostream> #include <string> int main() { std::string s1 = "Test"; // std::string&& r1 = s1; // 错误:不能绑定到左值 const std::string& r2 = s1 + s1; // okay:到 const 的左值引用延长生存期 // r2 += "Test"; // 错误:不能通过到 const 的引用修改 std::string&& r3 = s1 + s1; // okay:右值引用延长生存期 r3 += "Test"; // okay:能通过到非 const 的引用修改 std::cout << r3 << '\n'; }
更重要的是,当函数同时具有右值引用和左值引用的重载时,右值引用重载绑定到右值(包含纯右值和亡值),而左值引用重载绑定到左值: 运行此代码 #include <iostream> #include <utility> void f(int& x) { std::cout << "lvalue reference overload f(" << x << ")\n"; } void f(const int& x) { std::cout << "lvalue reference to const overload f(" << x << ")\n"; } void f(int&& x) { std::cout << "rvalue reference overload f(" << x << ")\n"; } int main() { int i = 1; const int ci = 2; f(i); // 调用 f(int&) f(ci); // 调用 f(const int&) f(3); // 调用 f(int&&) // 若不提供 f(int&&) 重载则会调用 f(const int&) f(std::move(i)); // 调用 f(int&&) // 右值引用变量在用于表达式时是左值 int&& x = 1; f(x); // 调用 f(int& x) f(std::move(x)); // 调用 f(int&& x) }
这允许在适当时机自动选择移动构造函数、移动赋值运算符和其他具移动能力的函数(例如 std::vector::push_back())。 因为右值引用能绑定到亡值,故它们能指代非临时对象: int i2 = 42; int&& rri = std::move(i2); // 直接绑定到 i2 这使得可以将作用域中不再需要的对象移动出去: std::vector<int> v{1,2,3,4,5}; std::vector<int> v2(std::move(v)); // 绑定右值引用到 v assert(v.empty()); |
(C++11 起) |
转发引用转发引用是一种特殊的引用,它保持函数实参的值类别,使得能利用 std::forward 转发实参。转发引用是下列之一: 1) 函数模板的函数形参,其被声明为同一函数模板的类型模板形参的无 cv 限定的右值引用:
template<class T> int f(T&& x) { // x 是转发引用 return g(std::forward<T>(x)); // 从而能被转发 } int main() { int i; f(i); // 实参是左值,调用 f<int&>(int&), std::forward<int&>(x) 是左值 f(0); // 实参是右值,调用 f<int>(int&&), std::forward<int>(x) 是右值 } template<class T> int g(const T&& x); // x 不是转发引用:const T 不是无 cv 限定的 template<class T> struct A { template<class U> A(T&& x, U&& y, int* p); // x 不是转发引用:T 不是构造函数的类型模板形参 // 但 y 是转发引用 }; 2) auto&&,但当其从花括号包围的初始化器列表推导时则不是:
auto&& vec = foo(); // foo() 可以是左值或右值,vec 是转发引用 auto i = std::begin(vec); // 也可以 (*i)++; // 也可以 g(std::forward<decltype(vec)>(vec)); // 转发,保持值类别 for (auto&& x: f()) { // x 是转发引用;这是使用范围 for 循环的最安全方式 } auto&& z = {1, 2, 3}; // *不是*转发引用(初始化器列表的特殊情形) 参阅模板实参推导和 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 的左值引用能延长临时对象的生存期(参阅引用初始化中的规则和例外情况)。
若被指代对象被销毁(例如通过显式的析构函数调用),但存储尚未被解分配,则到生存期外的对象的引用仍能以有限的方式使用,且当在同一存储中重新创建对象时也可以变为有效(细节见在生存期之外进行访问)。