默认参数在函数声明中为形参指定默认值;调用时若省略对应实参,编译器以默认值补全。默认参数必须从参数列表右侧连续出现。本节说明语法、与重载的关系,以及多文件编译时的放置位置。

语法示例

C++ 默认参数运行
#include <iostream>
#include <string>

void greet(const std::string& name, const std::string& prefix = "Hello") {
    std::cout << prefix << ", " << name << std::endl;
}

int main() {
    greet("Ada");
    greet("Lin", "Hi");
    return 0;
}

输出:

Hello, Ada
Hi, Lin

因为 greet("Ada") 只写了一个实参,所以 prefix 用声明里的默认值 "Hello",打印 Hello, Ada。因为 greet("Lin", "Hi") 把第二个实参也写上了,所以不再用默认值,打印 Hi, Lin。

prefix 的类型是 const std::string&。默认值 "Hello" 是字符串字面量,可以绑定到这种只读引用;若形参写成非 const 的 std::string&,字面量就不能当作默认值。

连续默认规则

int volume(int w, int h = 1, int d = 1);  // 合法:右侧连续

// int bad(int w = 1, int h);              // 非法:非末尾参数不能有默认值

中间参数不能单独缺省:不能写“只省略第二个参数”而保留第三个有默认值,除非前面的参数也有默认值。多个参数均有默认值时,调用可只传前若干个实参。

声明与定义中的位置

默认参数应写在首次可见的声明中(通常在头文件)。若声明在头文件、定义在 .cpp,不要在定义处再写一套不同的默认值,否则同一翻译单元内外可能不一致。

// widget.hpp
void configure(int mode, bool verbose = false);

// widget.cpp
void configure(int mode, bool verbose) {  // 定义处不再写 = false
    // ...
}

与重载的关系

下列组合在调用 f(1) 时产生歧义,编译失败:

void f(int x);
void f(int x, int y = 0);

默认参数在调用点由编译器“展开”,并非运行时查表。修改头文件中的默认值后,须重新编译所有包含该声明的翻译单元。

机制作用
默认参数一个函数,省略尾部实参
重载多个函数,由实参类型/个数选择

二者可同时使用(如一个重载带默认的第二参数),但须保证每种调用形式只对应一个可行函数。

默认参数与指针、引用

void dump(const int* p = nullptr) {
    if (p) {
        std::cout << *p << std::endl;
    }
}

默认 nullptr 可表示未提供可选输入。默认实参在调用点求值:若写成 void f(int n = ++g);,每次省略实参的调用都会执行 ++g。每个调用点使用该翻译单元所见声明中的默认表达式。

虚函数上的默认参数

虚函数的默认实参按静态类型选取,不随动态绑定的目标函数变化。若基类声明 virtual void draw(int scale = 1),派生类重写为 void draw(int scale = 2),通过基类指针调用时仍使用 1。因此不要在虚函数的基类与派生声明中写不同的默认值;需要变化的默认行为应放进函数体。

本节说明了默认参数的写法、连续规则、求值时机及与重载、虚函数的区分。下一节介绍递归:函数调用自身时的终止条件与栈深度。