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) | ||||||||
默认实参用于取代函数调用中缺失的尾部实参:
void point(int x = 3, int y = 4); point(1,2); // 调用 point(1,2) point(1); // 调用 point(1,4) point(); // 调用 point(3,4)
在函数声明中,在拥有默认实参的形参之后,所有后续形参必须
- 拥有在这个或同一作用域中先前的声明中所提供的默认实参;或
int x(int = 1, int); // 错误,假定没有先前的 x 的声明 void f(int n, int k = 1); void f(int n = 0, int k); // OK:同一作用域中之前的声明为 k 提供了默认实参 void g(int, int = 7); void h() { void g(int = 1, int); // 错误:不在同一作用域 }
template<class ...T> struct C { void f(int n = 0, T...); }; C<int> c; // OK;实例化了声明 void C::f(int n = 0, int)
template<class...T> void h(int i = 0, T... args); // ok |
(C++11 起) |
省略号不是形参,故可以跟在带有默认实参的形参之后。
int g(int n = 0, ...); // ok
默认实参仅允许于函数声明和 lambda 表达式 (C++14 起)的形参列表中出现,而不允许在函数指针、到函数的引用,或在 typedef 声明中出现。模板形参列表为其默认模板实参使用了类似的语法。
对于非模板函数,当在同一作用域中重声明函数时,可以向已声明的函数添加默认实参。在函数调用点,可用的默认实参是由该函数所有可见的声明中所提供的默认实参的并集。重声明不能为已有可见默认值的实参引入默认值(即使值相同)。内层作用域中的重声明不从外层作用域获得默认实参。
void f(int, int); // #1 void f(int, int = 7); // #2 OK :添加到默认 void h() { f(3); // #1 与 #2 在作用域中;进行对 f(3,7) 的调用 void f(int =1, int); // 错误:内层作用域声明不获得默认实参 } void m() { // 新作用域开始 void f(int, int); // 内层作用域声明;无默认实参。 f(4); // 错误:调用 f(int, int) 的实参不足 void f(int, int = 6); f(4); // OK:调用 f(4,6); void f(int, int = 6); // 错误:不能在同一作用域中重声明默认实参 } void f(int = 1, int); // #3 OK,向 #2 添加默认实参 void n() { // 新作用域开始 f(); // #1、#2 及 #3 在作用域中:调用 f(1, 7); }
若某个 inline 函数声明于不同翻译单元中,则默认实参的积累集必须在每个翻译单元的结尾相同。
若在不同翻译单元的同一命名空间作用域中声明一个非 inline 函数,则对应的默认实参若存在则必须相同(但一些某些翻译单元中可以缺少一些默认实参)。 |
(C++20 起) |
若 friend 声明指定了默认实参,则它必须是友元函数定义,且该翻译单元中不允许此函数的其他声明。
using 声明将已知的默认实参集承接过来,且若向函数的命名空间中添加更多默认形参,则这些默认实参在这条 using 声明可见的任何位置均亦为可见
namespace N { void f(int, int = 1); } using N::f; void g() { f(7); // 调用 f(7, 1); f(); // 错误 } namespace N { void f(int = 2, int); } void h() { f(); // 调用 f(2, 1); }
对默认实参中使用的名字进行查找,检查可访问性,并绑定于声明点,但在函数调用点才执行:
int a = 1; int f(int); int g(int x = f(a)); // f 的查找找到 ::f,a 的查找找到 ::a // 不使用 ::a 的值,它在此点为 1 void h() { a = 2; // 更改 ::a 的值 { int a = 3; g(); // 调用 f(2),然后以结果调用 g() } }
对于非模板类的成员函数,类外的定义中允许出现默认实参,并与类体内的声明所提供的默认实参组合。若类外的默认实参会使成员函数变成默认、复制或移动构造函数,则程序非良构。对于类模板的成员函数,所有默认实参必须在成员函数的初始声明处提供。
class C { void f(int i = 3); void g(int i, int j = 99); C(int arg); // 非默认构造函数 }; void C::f(int i = 3) { // 错误:默认实参已指定于类作用域 } void C::g(int i = 88, int j) { // OK:此翻译单元中,可无实参调用 C::g } C::C(int arg = 1) { // 错误:使之变成了默认构造函数 }
虚函数的覆盖函数不会从基类定义获得默认实参,而在进行虚函数调用时,默认实参根据对象的静态类型确定(注意:这可以通过非虚接口模式避免)。
struct Base { virtual void f(int a = 7); }; struct Derived : Base { void f(int a) override; }; void m() { Derived d; Base& b = d; b.f(); // OK:调用 Derived::f(7) d.f(); // 错误:无默认实参 }
默认实参中不允许使用局部变量,除非用于不求值语境中 (C++14 起):
void f() { int n = 1; extern void g(int x = n); // 错误:局部变量不能为默认 extern void h(int x = sizeof n); // CWG 2082 起 OK }
默认实参中不允许 this 指针:
class A { void f(A* p = this) { } // 错误:不允许 this };
默认实参中不允许使用非静态的类成员(即使它们不被求值),除非用于构成成员指针或在成员访问表达式中使用。
int b; class X { int a; int mem1(int i = a); // 错误:不能使用非静态数据成员 int mem2(int i = b); // OK:查找找到静态成员 X::b static int b; };
默认实参中不允许使用函数形参(即使它们不被求值) (C++14 前)除非它们不被求值 (C++14 起)。注意,形参列表中较早出现的形参已在作用域中:
int a; int f(int a, int b = a); // 错误:形参用作默认实参 int g(int a, int b = sizeof a); // CWG 2082 为止错误 // CWG 2082 后 OK:用于不求值语境是 OK 的
默认实参不是函数类型的一部分
int f(int = 0); void h() { int j = f(1); int k = f(); // 调用 f(0); } int (*p1)(int) = &f; int (*p2)() = &f; // 错误:f 的类型是 int(int)
若 lambda 表达式出现于默认实参中,则它不能显式或隐式俘获任何内容。
void f2() { int i = 1; void g1(int = ([i]{ return i; })()); // 错误:俘获某些内容 void g2(int = ([i]{ return 0; })()); // 错误:俘获某些内容 void g3(int = ([=]{ return i; })()); // 错误:俘获某些内容 void g4(int = ([=]{ return 0; })()); // OK:无俘获 void g5(int = ([]{ return sizeof i; })()); // OK:无俘获 }
除了函数调用运算符外,运算符函数不应有默认参数。
class C { int operator[](int i = 0); // 非良构 int operator()(int x = 0); // ok };
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 2082 | C++14 | 曾禁止默认实参在不求值语境中使用局部变量 | 允许不求值语境的使用 |