默认参数必须从右向左连续设置,即一旦某参数设默认值,其右侧所有参数都必须有默认值;只能在声明中指定,默认值须为编译期常量,调用时按位置匹配且不可跳过中间参数。

默认参数必须从右往左连续设置
函数声明时,一旦某个参数设了默认值,它右边的所有参数都必须有默认值。编译器不支持跳着设,比如 void f(int a, int b = 1, int c) 是非法的——c 没默认值但出现在已设默认值的 b 右侧。
常见错误是想“只给中间参数设默认值”,结果编译报错:default argument given for parameter X after previous default arguments。解决办法只有两个:要么补全右侧所有参数的默认值,要么调整参数顺序,把带默认值的参数全挪到末尾。
- 推荐写法:
void log(const std::string& msg, int level = 1, bool sync = false) - 错误写法:
void log(const std::string& msg = "", int level, bool sync = false)(msg有默认值,但level没有且在中间) - 声明和定义要分开时,**默认值只能写在声明里**(通常是头文件),定义(.cpp)中不能再写
默认参数不能是未定义的变量或非常量表达式
默认值必须在编译期可确定,不能依赖运行时状态。比如 int g_counter 这种全局变量、std::time(nullptr) 这类函数调用,都不能当默认参数。
容易踩的坑是误以为 const int x = 42; 在头文件里定义后就能用作默认值——其实不行,除非加 constexpr 或直接写字面量。因为普通 const 变量不一定是编译期常量(尤其跨 TU 时)。
- 合法:
void f(int x = 42)、void f(int x = std::numeric_limits(需::max()) constexpr支持) - 非法:
void f(int x = g_counter)、void f(std::string s = std::string("hi"))(构造函数调用非字面量) - 替代方案:用函数重载,或把逻辑移到函数体内处理
调用时传参会完全覆盖默认参数,不存在“部分覆盖”
C++ 的参数匹配是位置驱动的,不是名称驱动的。你传了几个实参,就从左到右依次绑定前几个形参,剩下的才取默认值。没有类似 Python 的关键字参数机制。
这意味着没法跳过某个参数只改后面的默认值。例如 void send(const std::string& to, const std::string& subject = "Hi", int delay_ms = 0),你想只改 delay_ms 却保留 subject 默认值,就必须显式写出 subject 的值:send("a@b.c", "Hi", 5000)。
- 没捷径,也不能写成
send("a@b.c", , 5000)(语法错误) - 如果这种调用很常见,建议拆成多个重载函数,或改用参数对象(如
SendOptions结构体) - 注意:重载 + 默认参数混用时,可能引发二义性,编译器会报
call is ambiguous
模板函数也能设默认参数,但要注意实例化时机
函数模板的默认参数在模板实例化时才被检查,所以错误可能延迟到调用点才暴露。比如模板参数类型不支持默认值表达式中的操作,就会在具体调用时编译失败。
更隐蔽的问题是:不同特化版本可能对同一默认参数有不同理解。例如 template,对 int 没问题,但对某些自定义类型,T{} 可能不合法或含义不同。
- 安全做法:默认值尽量用字面量或
std::declval配合() decltype推导(需 SFINAE 或 C++20 concepts 约束) - 避免在模板默认参数里调用用户自定义的构造函数或运算符
- 测试时至少覆盖基础类型(
int,std::string)和空类类型,确认默认值行为一致
默认参数看着简单,但和重载、模板、链接规则一碰就容易出人意料。最稳妥的做法是:只在确实能减少调用方重复代码时才用,且始终把它们放在参数列表最右边,值限定为字面量或 constexpr 表达式。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











