章节目录

默认参数

本节阅读量:

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

1
2
3
4
5
void print(int x, int y=10) // 10 是默认的参数值
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

进行函数调用时。如果调用方提供了对应值,则以传递的值为准。如果调用方不提供对应的值,则使用默认参数的值。

考虑以下程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#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); // 参数 y 使用默认值 4, 就像调用的是 print(3, 4)

    return 0;
}

该程序产生以下输出:

1
2
3
4
x: 1
y: 2
x: 3
y: 4

在第一个函数调用中,调用方为这两个参数提供了显式值,因此使用这些值。在第二个函数调用中,调用方省略了第二个参数,因此使用了默认值4。

请注意,必须使用等号来指定默认参数。使用括号或大括号不符合语法:

1
2
3
void foo(int x = 5);   // ok
void goo(int x ( 5 )); // 编译失败
void boo(int x { 5 }); // 编译失败

也许令人惊讶的是,默认参数由编译器在函数调用点处理。在上面的例子中,当编译器看到print(3)时,它将把这个函数调用重写为print(3, 4),以便传递值的数量与参数的数量匹配。然后,重写的函数调用按常规工作。


何时使用默认参数

当函数需要具有合理默认值,但您希望调用方可以根据需要覆盖该值时,默认参数是一个很好的功能。

例如,下面是两个通常使用默认参数的函数原型:

1
2
int rollDie(int sides=6);
void openLogFile(std::string filename="default.log");

多个默认参数

函数可以具有多个具有默认值的参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#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 使用默认参数
    print(1); // y z 使用默认参数
    print(); // x y z 使用默认参数

    return 0;
}

产生以下输出:

1
2
3
4
Values: 1 2 3
Values: 1 2 30
Values: 1 20 30
Values: 10 20 30

C++不支持这样的函数调用语法,如print(, , 3)(作为一种在使用x和y的默认参数时为z提供显式值的方法)。这有一个主要后果:

如果一个参数使用了默认值,那么所有后续直到结束的参数,都必须使用默认值

同时也不允许出现以下情况:

1
void print(int x=10, int y); // 不允许

无法重新声明默认参数

一旦声明,默认参数就不能重新声明(在同一文件中)。这意味着对于具有前向声明和函数定义的函数,默认参数可以在前向声明或函数定义中单独声明,但不能同时在两者中声明。

1
2
3
4
5
6
7
8
9
#include <iostream>

void print(int x, int y=4); // 前向声明

void print(int x, int y=4) // error: 重新定义默认参数
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

最佳实践是在前向声明中声明默认参数,而不是在函数定义中声明,因为前向声明更可能被其他文件看到(特别是在头文件中)。

在foo.h中:

1
2
3
4
#ifndef FOO_H
#define FOO_H
void print(int x, int y=4);
#endif

在main.cpp中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "foo.h"
#include <iostream>

void print(int x, int y)
{
    std::cout << "x: " << x << '\n';
    std::cout << "y: " << y << '\n';
}

int main()
{
    print(5);

    return 0;
}

注意,在上面的例子中,我们能够为函数print()使用默认参数,因为main.cpp引用了foo.h,它具有定义默认参数的前向声明。


默认参数和函数重载

可以重载具有默认参数的函数。例如,允许以下操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <string>

void print(std::string)
{
}

void print(char=' ')
{
}

int main()
{
    print("Hello, world"); // 调用的是 print(std::string)
    print('a'); // 调用的是 print(char)
    print(); // 调用的是 print(char)

    return 0;
}

对print()的函数调用的作用类似于用户显式调用了print(’’),它解析为print(char)。

现在考虑这种情况:

1
2
3
void print(int x);
void print(int x, int y = 10);
void print(int x, double y = 20.5);

具有默认值的参数将能够区分重载函数(意味着上面的将通过编译)。然而,这样的函数在调用时可能会导致潜在的不明确匹配。例如:

1
2
3
print(1, 2); // 解析到 print(int, int)
print(1, 2.5); // 解析到 print(int, double) 
print(1); // 不明确匹配的函数调用

在最后一种情况下,编译器无法判断print(1)应该解析为print(int),还是第二个参数具有默认值的两个函数之一。结果是一个不明确的函数调用。


总结

默认参数提供了一种有用的机制,用户可以选择覆盖或不覆盖参数的默认值。它在C++中经常使用,您将在以后的课程中看到它。


11.3 函数的delete说明符

上一节

11.5 函数模板

下一节