默认实参 - C++函数参数的默认值设置指南

默认实参是为函数形参提供的默认值。例如:

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),再按常规流程处理。

默认实参的用途

  1. 为具有合理默认值的形参提供默认选择,同时允许调用者覆盖。

    int rollDie(int sides = 6);
    void openLogFile(std::string filename = "default.log");
    
  2. 向已有函数追加新形参而不破坏旧代码:

    • 若新增形参无默认值,所有旧调用必须修改;
    • 若新增形参带默认值,旧调用继续生效。

作者注 因调用者可选择是否提供,带默认值的形参有时被称为“可选参数”,但该术语也用于其它场景,建议避免使用。

多个默认实参

函数可有多个带默认值的形参:

#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 课 —— 函数指针)。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"