默认实参是为函数形参提供的默认值。例如:
void print(int x, int y = 10) // 10 是默认实参
{
std::cout << "x: " << x << '\n';
std::cout << "y: " << y << '\n';
}
调用函数时,调用者可选择为带默认实参的形参提供实参。
- 若提供,则使用所给值;
- 若省略,则使用默认值。
示例程序:
#include <iostream>
void print(int x, int y = 4) // 4 为默认实参
{
std::cout << "x: " << x << '\n';
std::cout << "y: " << y << '\n';
}
int main()
{
print(1, 2); // y 取用户提供的 2
print(3); // 等效于 print(3, 4),y 取默认值 4
return 0;
}
输出:
x: 1
y: 2
x: 3
y: 4
注意:
- 必须用等号指定默认实参;括号或花括号均非法:
void foo(int x = 5); // 正确 void goo(int x (5)); // 错误 void boo(int x {5}); // 错误
关键洞察
默认实参由编译器在调用点插入,即在看到 print(3)
时,编译器会改写成 print(3, 4)
,再按常规流程处理。
默认实参的用途
为具有合理默认值的形参提供默认选择,同时允许调用者覆盖。
int rollDie(int sides = 6); void openLogFile(std::string filename = "default.log");
向已有函数追加新形参而不破坏旧代码:
- 若新增形参无默认值,所有旧调用必须修改;
- 若新增形参带默认值,旧调用继续生效。
作者注 因调用者可选择是否提供,带默认值的形参有时被称为“可选参数”,但该术语也用于其它场景,建议避免使用。
多个默认实参
函数可有多个带默认值的形参:
#include <iostream>
void print(int x = 10, int y = 20, int z = 30)
{
std::cout << "Values: " << x << " " << y << " " << z << '\n';
}
int main()
{
print(1, 2, 3); // 全部显式
print(1, 2); // z 取 30
print(1); // y, z 取默认值
print(); // x, y, z 均取默认值
}
输出:
Values: 1 2 3
Values: 1 2 30
Values: 1 20 30
Values: 10 20 30
C++23 仍不支持 print(,,3)
这种跳过中间参数的语法,因此:
- 显式实参必须自左向右连续提供;
- 一旦某形参有默认值,其后所有形参必须也有默认值。
规则
若某形参设有默认值,则其右侧所有形参也必须设有默认值。
示例:
void print(std::string_view sv = "Hello", double d = 10.0);
int main()
{
print(); // 正确
print("Macaroni"); // d 取 10.0
print(20.0); // 错误:无法跳过 sv
}
若多个形参有默认值,应将最常用的默认值放在最左侧。
默认实参不可重复声明,且必须在使用前可见
// 错误:重复定义
void print(int x, int y = 4); // 前向声明
void print(int x, int y = 4) // 定义处再写默认值 → 编译错误
{ /*...*/ }
// 错误:使用前未声明默认值
void print(int x, int y); // 无默认值
int main()
{
print(3); // 编译错误
}
void print(int x, int y = 4) { /*...*/ }
// 最佳实践:前向声明中给出默认值
// foo.h
#ifndef FOO_H
#define FOO_H
void print(int x, int y = 4);
#endif
// main.cpp
#include "foo.h"
void print(int x, int y) { /*...*/ }
最佳实践
若函数有前向声明(尤其在头文件),把默认实参写在前向声明中;否则写在定义处。
默认实参与函数重载
带默认实参的函数仍可重载:
#include <iostream>
#include <string_view>
void print(std::string_view s)
{
std::cout << s << '\n';
}
void print(char c = ' ')
{
std::cout << c << '\n';
}
int main()
{
print("Hello, world"); // 匹配 print(std::string_view)
print('a'); // 匹配 print(char)
print(); // 等效于 print(' ')
}
print()
实际调用 print(char)
,使用默认实参 ' '
。
注意:默认实参不属于函数签名,因此下列声明为合法重载:
void print(int x); // 签名:print(int)
void print(int x, int y = 10); // 签名:print(int,int)
void print(int x, double y = 20.5); // 签名:print(int,double)
默认实参可能引发二义性
void foo(int x = 0) {}
void foo(double d = 0.0) {}
int main()
{
foo(); // 二义性:无法区分 foo(0) 还是 foo(0.0)
}
更复杂示例:
void print(int x);
void print(int x, int y = 10);
void print(int x, double y = 20.5);
int main()
{
print(1, 2); // 正确:print(int,int)
print(1, 2.5); // 正确:print(int,double)
print(1); // 二义性:无法选择 print(int) / print(int,int) / print(int,double)
}
若确实需要调用 print(int)
,可显式写出第二个实参或改用函数指针(20.1 课)。
函数指针调用不支持默认实参
函数指针调用时,默认实参不被考虑,因此可借此规避二义性(20.1 课 —— 函数指针)。