Default arguments
允许在不提供一个或多个尾部参数的情况下调用函数。
通过在 函数声明 的 参数列表 中对参数使用以下语法来表示。
attr
(可选)
decl-specifier-seq
declarator
=
initializer
|
(1) | ||||||||
attr
(可选)
decl-specifier-seq
abstract-declarator
(可选)
=
initializer
|
(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); // 正确:参数“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; instantiates declaration void C::f(int n = 0, int)
template<class... T> void h(int i = 0, T... args); // OK |
(since C++11) |
省略号不是参数,因此可以跟在带有默认参数的参数后面:
int g(int n = 0, ...); // 正确
默认实参仅允许出现在 函数声明 和 lambda表达式 的参数列表中, (C++11 起) 不允许出现在函数指针声明、函数引用声明或 typedef 声明中。模板参数列表使用类似语法表示其 默认模板实参 。
对于非模板函数,如果函数在同一作用域内被重新声明,则可以为已声明的函数添加默认参数。在函数调用点,默认参数是所有可见声明中提供的默认参数的并集。重新声明不能为已存在可见默认参数的形参引入默认参数(即使值相同)。内部作用域中的重新声明不会从外部作用域获取默认参数。
void f(int, int); // #1 void f(int, int = 7); // #2 正确:添加了默认参数 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); // 正确:调用 f(4, 6) void f(int, int = 6); // 错误:第二个参数已存在 // 默认参数(即使参数值相同) } void f(int = 1, int); // #3 正确,为 #2 添加默认参数 void n() { // 新作用域开始 f(); // #1、#2 和 #3 在作用域内:调用 f(1, 7) }
如果 inline 函数在不同翻译单元中声明,每个翻译单元末尾的默认参数集合必须保持一致。
|
如果同一命名空间作用域中的非内联函数在不同翻译单元中被声明,若存在对应的默认参数,则这些默认参数必须保持一致(但某些翻译单元中可以缺少部分默认参数)。 |
(since 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() } }
对于 成员函数 属于非 模板化 类的情况,允许在类外定义中提供默认参数,这些参数将与类体内声明提供的默认参数合并。如果这些类外默认参数会将成员函数转变为默认构造函数或复制 /移动 (C++11 起) 构造函数/赋值运算符(这将导致调用歧义),则程序非良构。对于模板化类的成员函数,所有默认参数必须在成员函数的初始声明中提供。
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) {} // 正确:在此翻译单元中, // 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(); // 正确:调用 Derived::f(7) d.f(); // 错误:无默认参数 }
局部变量不允许在默认参数中使用,除非它们是 不被求值 的:
void f() { int n = 1; extern void g(int x = n); // 错误:局部变量不能作为默认参数 extern void h(int x = sizeof n); // 根据CWG 2082提案允许使用 }
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); // 正确:查找找到 X::b,即静态成员 int mem3(int X::* i = &X::a); // 正确:非静态成员可以使用 int mem4(int i = x.a); // 正确:在成员访问表达式中 static X x; static int b; };
默认实参在每次调用函数且未提供对应参数时都会被求值。函数参数不允许出现在默认实参中,除非它们 不被求值 。注意:在参数列表中较早出现的参数处于 作用域 内:
int a; int f(int a, int b = a); // 错误:在默认参数中使用了参数a int g(int a, int b = sizeof a); // 在CWG 2082决议前是错误用法 // 决议后正确:在未求值上下文中的使用是允许的
默认参数不属于函数类型:
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)
除 函数调用运算符 和 下标运算符 (自 C++23 起) 外的运算符函数不能具有默认参数:
class C { int operator++(int i = 0); // 非良构 int operator[](int j = 0); // 自 C++23 起合法 int operator()(int k = 0); // 合法 };
|
显式对象形参 不能拥有默认实参: struct S { void f(this const S& = S{}); }; // ill-formed |
(since C++23) |
说明
当参数名称缺失时,可能需要使用空格来避免形成复合赋值标记(参见 最大吞噬原则 )。
void f1(int*=0); // 错误,此处“*=”不符合预期 void g1(const int&=0); // 错误,此处“&=”不符合预期 void f2(int* = 0); // 正确 void g2(const int& = 0); // 正确 void h(int&&=0); // 即使没有空格也正确,此处“&&”是一个独立标记
缺陷报告
下列行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 217 | C++98 | 可以向类模板的非模板成员函数添加默认参数 | 禁止此操作 |
| CWG 1344 | C++98 | 在成员函数类外定义中添加的默认参数可能将其变为特殊成员函数 | 禁止此操作 |
| CWG 1716 | C++98 | 每次调用函数时都会对默认参数求值,即使调用者已提供实参 | 仅当未提供对应形参的实参时才进行求值 |
| CWG 2082 | C++98 | 禁止在未求值上下文中使用局部变量和前序形参作为默认参数 | 允许未求值上下文的使用 |
| CWG 2233 | C++11 | 从参数包展开的形参不能出现在带有默认参数的形参之后 | 允许此用法 |
| CWG 2683 | C++98 | 类模板嵌套类的成员函数的类外定义可以具有默认参数 | 禁止此操作 |