默认参数
  本节阅读量: 
  
  默认参数是为函数参数提供的默认值。例如:
| 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++中经常使用,您将在以后的课程中看到它。
        
        
        