代码存储位置:shanchuann /Modern_CPP
声明
在函数声明中,当我们想给函数形参声明为默认实参时,我们需要确保在当前作用域下 该形参后的形参也是_默认形参_ 。
1 2 3 4 5 6 7 8 9 10
| void f(int, int, int = 10); void f(int, int = 20, int); void f(int = 30, int, int); void f(int a, int b, int c) { cout << a << " " << b << " " << c << endl; } int main() { f(); }
|
当我们将void f(int, int = 20, int);
移动至第一行时,会显示以下ERROR :
1 2 3
| void f(int, int = 20, int); void f(int, int, int = 10); void f(int = 30, int, int);
|
1 2 3 4 5
| 1>D:\cpp language\现代C++\默认实参.cpp(6,27): error C2548: “f”: 缺少形参 3 的默认实参 1>D:\cpp language\现代C++\默认实参.cpp(7,24): error C2572: “f”: 重定义默认参数 : 参数 1 1> D:\cpp language\现代C++\默认实参.cpp(6,6): 1> 参见“f”的声明 1>D:\cpp language\现代C++\默认实参.cpp(8,27): error C2548: “f”: 缺少形参 2 的默认实参
|
第二个形参声明为默认实参是没有问题的,但前提是第三个参数也被声明为了默认实参。
除非该形参是从某个形参包展开得到或是函数形参包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| template<class...T> struct X { void f(int n = 0, T...args) { cout << n << endl; }; };
template<class...Args> void f_(int n = 6, Args...args) {
}
int main() { X<>().f(); X<int>().f(1, 2); }
|
类作用域
对于非模板类的 成员函数 ,类外的初始化 中允许出现默认实参,并与类内声明时提供的默认实参组合 。如果类外的默认实参会使成员函数变成默认构造函数或复制 /移动构造函数 /赋值运算符,那么程序非良构(指程序存在语法错误或可诊断的语义错误)。
合法的默认参数组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class MyClass { public: void normalFunction(int a, int b = 10); MyClass(int x = 0); };
void MyClass::normalFunction(int a = 5, int b) { cout << "a = " << a << ", b = " << b << endl; }
MyClass::MyClass(int x) {}
int main() { MyClass obj; obj.normalFunction(); obj.normalFunction(20); obj.normalFunction(30, 40); }
|
组合规则:
- 类内:
normalFunction(int a, int b = 10)
- 类外:
normalFunction(int a = 5, int b)
- 最终签名:
normalFunction(int a = 5, int b = 10)
输出
1 2 3
| a = 5, b = 10 a = 20, b = 10 a = 30, b = 40
|
非良构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Problematic { public: Problematic(int x); Problematic(const Problematic& other); };
Problematic::Problematic(int x = 0) { }
Problematic::Problematic(const Problematic& other = Problematic(0)) { }
|
对于类模板的成员函数,所有默认实参必须在成员函数的初始声明处提供。
1 2 3 4 5 6 7 8
| class C { void f(int a = 10); void g(int a, int b = 20); C(int a); }; void C::f(int a = 20) {} void C::g(int a = 10, int b) {} C::C(int a = 20) {}
|
1 2 3
| 1>D:\cpp language\现代C++\默认实参.cpp(8,19): error C2572: “C::f”: 重定义默认参数 : 参数 1 1> D:\cpp language\现代C++\默认实参.cpp(4,10): 1> 参见“C::f”的声明
|
C::f
重定义了默认实参
C::C
使得构造函数变为了默认构造函数
虚函数与默认实参
虚函数的覆盖函数不会从基类定义获得默认实参,而在进行虚函数调用时,默认实参根据对象的静态类型确定 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct Base { virtual void f(int a = 10) { cout << "Base::f, a = " << a << endl; } }; struct Derived : Base { void f(int a) override { cout << "Derived::f, a = " << a << endl; } }; int main() { Derived d; Base* b = &d; b->f(); return 0; }
|
输出:Derived::f, a = 10
静态类型:对程序进行编译时分析得到的表达式的类型 被称为表达式的静态类型,程序执行时静态类型不会更改。
动态类型:如果某个 泛左值表达式(左值和将亡值的合称) 指代某个多态对象,那么他的最终派生对象的类型被称为他的动态类型。
1 2 3 4 5 6 7
| struct B { virtual ~B() {} }; struct D : B {}; D d; B* pb = &d;
|
由此可见,静态类型为父类,所以最后的打印结果为"Base::f, a = 10 "
不求值语境
默认实参能在_不求值语境( typeid 、 sizeof 、 noexcept 和 decltype)_ 中使用局部变量
1 2 3 4 5 6 7 8
| int main() { int f = 0; void f2(int n = sizeof f); f2(); } void f2(int n) { cout << n << endl; }
|
在Visual Studio 2022 内置的 MSVC编译器中会报错
1 2 3 4 5 6 7 8
| 1>D:\cpp language\现代C++\默认实参.cpp(6,28): error C2587: “f”: 非法将局部变量作为默认参数 1> D:\cpp language\现代C++\默认实参.cpp(5,6): 1> 参见“f”的声明 1>D:\cpp language\现代C++\默认实参.cpp(7,5): error C2660: “f2”: 函数不接受 0 个参数 1> D:\cpp language\现代C++\默认实参.cpp(6,10): 1> 参见“f2”的声明 1> D:\cpp language\现代C++\默认实参.cpp(7,5): 1> 尝试匹配参数列表“()”时
|
但采用G++编译则是正常打印结果,这只是编译器差异

类成员
默认实参中不能使用非静态类成员(即使他们不被求值) ,除非用于构成成员指针或在成员访问表达式中使用。
1 2 3 4 5 6 7 8 9 10
| struct X { int n = 10; static const int sn = 20; void f(int n = sizeof + n) { cout << n << endl; } void f_(int n = sn) { cout << n << endl; } }; int main() { X().f(); X().f_(); }
|
1 2 3 4
| 1>D:\cpp language\现代C++\默认实参.cpp(7,16): error C2648: “X::n”: 将成员作为默认参数使用要求静态成员 1> D:\cpp language\现代C++\默认实参.cpp(5,9): 1> 参见“X::n”的声明 1>D:\cpp language\现代C++\默认实参.cpp(7,16): error C2355: “this”: 只能在非静态成员函数或非静态数据成员初始值设定项的内部引用
|
所以即使是不求值的语境同样不行。