命名空间
变体
操作

默认参数

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 

允许函数在不提供一个或多个尾随参数的情况下被调用。

函数声明参数列表 中使用以下语法对参数进行指示。

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); // calls point(1, 2)
point(1);    // calls point(1, 4)
point();     // calls point(3, 4)

在函数声明中,在具有默认参数的参数之后,所有后续参数必须

  • 在该声明或来自同一作用域的先前声明中提供默认参数
int x(int = 1, int); // Error: only the trailing parameters can have default arguments
                     // (assuming there's no previous declaration of x)
 
void f(int n, int k = 1);
void f(int n = 0, int k); // OK: the default argument of `k` is provided by
                          // the previous declaration in the same scope
 
void g(int, int = 7);
 
void h()
{
    void g(int = 1, int); // Error: not the same scope
}
  • ...除非参数是从参数包扩展的
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
(自 C++11 起)

省略号不是参数,因此可以跟随具有默认参数的参数

int g(int n = 0, ...); // OK

默认参数仅允许在 函数声明lambda 表达式 中,(自 C++11 起) 并且不允许在指向函数的指针、指向函数的引用或 typedef 声明的声明中。模板参数列表对其 默认模板参数 使用类似的语法。

对于非模板函数,如果函数在同一作用域中被重新声明,则可以向已声明的函数添加默认参数。在函数调用时,默认参数是所有可见的函数声明中提供的默认参数的并集。重新声明不能为已可见默认参数的参数引入默认参数(即使值相同)。内部作用域中的重新声明不会从外部作用域获取默认参数。

void f(int, int);     // #1
void f(int, int = 7); // #2 OK: adds a default argument
 
void h()
{
    f(3); // #1 and #2 are in scope; makes a call to f(3,7)
    void f(int = 1, int); // Error: the default argument of the second
                          // parameter is not acquired from outer scopes
}
 
void m()
{ // new scope begins
    void f(int, int); // inner scope declaration; has no default argument.
    f(4); // Error: not enough arguments to call f(int, int)
    void f(int, int = 6);
    f(4); // OK: calls f(4, 6);
    void f(int, int = 6); // Error: the second parameter already has a
                          // default argument (even if the values are the same)
}
 
void f(int = 1, int); // #3 OK, adds a default argument to #2
 
void n()
{ // new scope begins
    f(); // #1, #2, and #3 are in scope: calls f(1, 7);
}

如果在不同的翻译单元中声明了一个 inline 函数,则在每个翻译单元结束时累积的默认参数集必须相同。

如果在不同的翻译单元中在同一命名空间作用域中声明了一个非内联函数,则如果存在,相应的默认参数必须相同(但某些默认参数可能在某些翻译单元中不存在)。

(自 C++20 起)

如果 friend 声明指定了默认参数,则它必须是 friend 函数定义,并且不允许在翻译单元中进行其他声明。

using 声明 会将已知默认参数集传递过来,并且如果稍后在函数的命名空间中添加了更多默认参数,那么这些默认参数在 using 声明可见的任何地方都可见。

namespace N
{
    void f(int, int = 1);
}
 
using N::f;
 
void g()
{
    f(7); // calls f(7, 1);
    f();  // error
}
 
namespace N
{
    void f(int = 2, int);
}
 
void h()
{
    f();  // calls f(2, 1);
}

在默认参数中使用的名称在声明时会被查找、检查 可访问性 并进行绑定,但在函数调用时会被执行。

int a = 1;
 
int f(int);
 
int g(int x = f(a)); // lookup for f finds ::f, lookup for a finds ::a
                     // the value of ::a, which is 1 at this point, is not used
 
void h()
{
    a = 2; // changes the value of ::a
    {
        int a = 3;
        g(); // calls f(2), then calls g() with the result
    }
}

对于非 模板 类的 成员函数,默认参数允许在类体外部定义,并且与类体内部声明提供的默认参数合并。如果这些类体外部的默认参数会将成员函数变成默认构造函数或复制/移动(自 C++11 起) 构造函数/赋值运算符(这会使调用模棱两可),则程序格式错误。对于模板类的成员函数,所有默认参数必须在成员函数的初始声明中提供。

class C
{
    void f(int i = 3);
    void g(int i, int j = 99);
    C(int arg); // non-default constructor
};
 
void C::f(int i = 3) {}         // error: default argument already
                                // specified in class scope
 
void C::g(int i = 88, int j) {} // OK: in this translation unit,
                                // C::g can be called with no argument
 
C::C(int arg = 1) {}            // Error: turns this into a default constructor

函数的覆盖者不会从基类声明中获取默认参数,并且当进行虚函数调用时,默认参数是根据对象的静态类型确定的(注意:这可以通过 非虚接口 模式来避免)。

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: calls Derived::f(7)
    d.f(); // Error: no default argument
}

默认参数中不允许使用局部变量,除非它们 不被求值

void f()
{
    int n = 1;
    extern void g(int x = n); // error: local variable cannot be a default argument
    extern void h(int x = sizeof n); // OK as of CWG 2082
}

在默认参数中不允许使用 `this` 指针。

class A
{
    void f(A* p = this) {} // error: this is not allowed
};

非静态类成员不允许在默认参数中使用(即使它们没有被求值),除非用于构成指向成员的指针或成员访问表达式。

int b;
 
class X
{
    int a;
    int mem1(int i = a); // error: non-static member cannot be used
    int mem2(int i = b); // OK: lookup finds X::b, the static member
    int mem3(int X::* i = &X::a); // OK: non-static member can be used
    int mem4(int i = x.a); // OK: in a member access expression
 
    static X x;
    static int b;
};

每次调用函数时,都会对默认参数进行求值,前提是相应参数没有传入实参。除了在不被求值的情况下,函数参数不允许在默认参数中使用。请注意,参数列表中较早出现的参数在作用域中。

int a;
 
int f(int a, int b = a); // Error: the parameter a used in a default argument
 
int g(int a, int b = sizeof a); // Error until resolving CWG 2082
                                // OK after resolution: use in unevaluated context is OK

默认参数不是函数类型的一部分。

int f(int = 0);
 
void h()
{
    int j = f(1);
    int k = f(); // calls f(0);
}
 
int (*p1)(int) = &f;
int (*p2)()    = &f; // Error: the type of f is int(int)

运算符函数不能有默认参数,除了函数调用运算符。

class C
{
    int operator[](int i = 0); // ill-formed
    int operator()(int x = 0); // OK
};

显式对象参数不能有默认参数。

struct S { void f(this const S& = S{}); }; // ill-formed
(自 C++23 起)

[edit] 注意

如果参数名称不存在,可能需要使用空格来避免复合赋值运算符。

void f1(int*=0);         // Error, '*=' is unexpected here
void g1(const int&=0);   // Error, '&=' is unexpected here
void f2(int* = 0);       // OK
void g2(const int& = 0); // OK
void h(int&&=0);         // OK even without spaces, '&&' is a token here

[edit] 缺陷报告

以下行为更改的缺陷报告被追溯应用于之前发布的 C++ 标准。

DR 应用于 已发布的行为 正确行为
CWG 217 C++98 可以向类模板的非模板成员函数添加默认参数
禁止
允许
CWG 1344 C++98 在类成员函数的类外定义中添加的默认参数可能会将其更改为特殊成员函数
禁止
允许
允许 C++98 CWG 1716
每次调用函数时都会对默认参数进行求值,即使调用者提供了参数
仅在没有为对应参数提供参数时进行求值
仅在没有为对应参数提供参数时进行求值
仅在没有为对应参数提供参数时进行求值
CWG 2082 C++98 默认参数被禁止在非求值上下文中使用局部变量和前面的参数
非求值上下文
允许使用
允许使用
CWG 2233 C++11 从参数包展开的参数不能出现在带默认参数的参数之后
禁止
允许
CWG 2683 C++98 类模板的嵌套类的成员函数的类外定义可以有默认参数
禁止
允许
允许