函数重载之函数的默认参数

昨天面试的时候,被问到函数重载时第三个参数为空,只有两个参数会不会调用三个参数的函数,我脱口而出就说不会,回来想想感觉好像不对,然后查了一下资料,发现真的错了


例子:int fun(int a,int b,int c=0) 

 fun(5,8)是可以调用上面那个函数的,但如果又存在一个函数int fun(int a,int b)

则,fun(5,8)因不知道调用哪个会出错。


详解如下:


一.形参&实参

形参和实参,虽然用了这么久了,不过概念上还是有点纠结的。这里简单总结一下:形参是说明参数类型的,实参就是函数实际操作的对象,我们定义一个函数的时候,写的那个是形参,我们调用函数的时候,给如的参数就是实参。

最近在百度知道上看到了一个关于形参实参最精辟的解释,无耻的引用一下:

比如说进女厕所,那就是女人才能进去 ,那么女人就是进女厕所这个操作的形参,林黛玉进去了,杨贵妃进去了,林黛玉,杨贵妃这些就是实参,李隆基要进的话那就类型不符

二.简单使用

C++函数支持默认参数,这是一个很方便的特性。我们在函数声明或者定义的时候,给函数的参数设置一个默认值,当调用时如果不给参数或者给出一部分参数,那么就使用函数设定的默认参数值。先看一个例子:

[cpp]  view plain  copy
  1. // C++Test.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <iostream>  
  6. using namespace std;  
  7.   
  8. void DefaultArguTest(int arg1, int arg2 = 2, int arg3 = 3)  
  9. {  
  10.     cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;  
  11. }  
  12.   
  13. int _tmain(int argc, _TCHAR* argv[])  
  14. {  
  15.       
  16.     //第2,3个参数给出了,则使用参数的值  
  17.     cout<<"No Default argu:"<<endl;  
  18.     DefaultArguTest(1,1,1);  
  19.     //第3个参数没给出,则使用默认值  
  20.     cout<<"Default argu3:"<<endl;  
  21.     DefaultArguTest(1,1);  
  22.     //第2,3个参数都没给出,使用默认值  
  23.     cout<<"Default argu2,3:"<<endl;  
  24.     DefaultArguTest(1);  
  25.   
  26.     system("pause");  
  27.     return 0;  
  28. }  
结果:

No Default argu:
1 1 1
Default argu3:
1 1 3
Default argu2,3:
1 2 3
请按任意键继续. . .


三.注意事项

感觉默认参数的知识点还是挺简单的,但是要注意的地方还是有不少的...

1.一般默认参数给出的位置都是函数的声明处,如果函数没有声明只有定义的时候,那就放在定义处。但是,如果函数有声明,那么就必须放在声明处。如果放在了定义的地方,那么会报出下面的错误:
error C2660: “DefaultArguTest”: 函数不接受 2 个参数
表明编译器并不知道给出了默认参数,仍按照我们输入参数个数不对处理的。而如果我们在声明和定义的地方都给出了默认参数也是不对的,会报出下面的错误:
DefaultArguTest”: 重定义默认参数 : 参数 3
所以,我们简单干脆的记住: 默认参数放在函数的声明处!

2. 如果左边的参数给出了默认参数,那么它右边的参数必须都有默认参数。这个地方也是容易犯错误的地方。

3.调用实参必须是连续的,即我们给出的参数,必须从左只有填入形参中,而右边没给的才用默认参数来补齐。

4.默认值可以是全局变量、全局常量, 甚至是一个函数。但不可以是局部变量。因为默认参数的调用是在编译时确定的,而局部变量位置与默认值在编译时无法确定。


四.默认参数和函数重载的冲突

默认参数和函数重载一起使用会导致冲突,看下面的例子:

[cpp]  view plain  copy
  1. // C++Test.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <iostream>  
  6. using namespace std;  
  7.   
  8. //函数声明  
  9. void DefaultArguTest(int arg1 = 1, int arg2 = 2, int arg3 = 3);  
  10. //重载  
  11. void DefaultArguTest();  
  12.   
  13. int _tmain(int argc, _TCHAR* argv[])  
  14. {  
  15.     //不给参数  
  16.     DefaultArguTest();  
  17.   
  18.     system("pause");  
  19.     return 0;  
  20. }  
  21.   
  22. //函数定义  
  23. void DefaultArguTest(int arg1, int arg2, int arg3)  
  24. {  
  25.     cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;  
  26. }  

错误如下:

 error C2668: “DefaultArguTest”: 对重载函数的调用不明确
1> 可能是“void DefaultArguTest(void)”
1> 或       “void DefaultArguTest(int,int,int)”

对于当我们不给参数的时候,默认的DefaultArguTest和无参数的DefaultArguTest都可能被调用,所以就造成了调用不明确的错误。


仔细想一下,为什么C++的默认构造函数在我们自己定义了构造函数就自动不生成了呢?

个人感觉,有可能是害怕我们自己定义构造函数时,如果加上默认参数,那么就和编译器为我们提供的默认构造函数冲突了,为了防止这种隐患,索性如果自己写了构造函数,那就不生成默认构造函数了。



五.覆写函数时不要更换默认参数

如果我没记错的话这是《Effectice C++》中的一条,我们在覆写函数的时候,绝对不能修改它的默认参数,因为这会导致一个非常难发现的BUG!正因为如此,我们如果在VS(带VA插件的,本人猜测这个是VA插件加入的)中覆写带有默认参数的成员函数时,它会默认的将默认参数给出,以注释的形式给出提醒:

[cpp]  view plain  copy
  1. // C++Test.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <iostream>  
  6. #include <string>  
  7. using namespace std;  
  8.   
  9. class Base  
  10. {  
  11. public:  
  12.     virtual void Print(int i = 1, int j = 2)  
  13.     {  
  14.         cout<<"In base: "<<i<<" "<<j<<endl;  
  15.     }  
  16. };  
  17.   
  18. class Child : public Base  
  19. {  
  20.     //我们覆写带有默认参数的函数,VA插件给出了提醒,这两个值都是有默认参数的  
  21.     void Print(int i /* = 1 */int j /* = 2 */)  
  22.     {  
  23.         cout<<"In Child: "<<i<<" "<<j<<endl;  
  24.     }  
  25. };  
  26.   
  27.   
  28.   
  29. int _tmain(int argc, _TCHAR* argv[])  
  30. {  
  31.     Base* base = new Child();  
  32.     base->Print();  
  33.       
  34.   
  35.     system("pause");  
  36.     return 0;  
  37. }  
结果:
In Child: 1 2
请按任意键继续. . .


但是,如果我们不信邪,偏偏要给子类加一个不同的默认参数,结果就会大大出乎我们的意料:

[cpp]  view plain  copy
  1. // C++Test.cpp : 定义控制台应用程序的入口点。  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5. #include <iostream>  
  6. #include <string>  
  7. using namespace std;  
  8.   
  9. class Base  
  10. {  
  11. public:  
  12.     virtual void Print(int i = 1, int j = 2)  
  13.     {  
  14.         cout<<"In base: "<<i<<" "<<j<<endl;  
  15.     }  
  16. };  
  17.   
  18. class Child : public Base  
  19. {  
  20. public:  
  21.     //我们手动的将默认参数修改了  
  22.     void Print(int i = 3, int j  = 4 )  
  23.     {  
  24.         cout<<"In Child: "<<i<<" "<<j<<endl;  
  25.     }  
  26. };  
  27.   
  28.   
  29.   
  30. int _tmain(int argc, _TCHAR* argv[])  
  31. {  
  32.     //静态绑定  
  33.     cout<<"Static bind:"<<endl;  
  34.     Child* child = new Child();  
  35.     child->Print();  
  36.   
  37.     //动态绑定  
  38.     cout<<"Dynamic bind:"<<endl;  
  39.     Base* base = new Child();  
  40.     base->Print();  
  41.       
  42.   
  43.     system("pause");  
  44.     return 0;  
  45. }  
结果:

Static bind:
In Child: 3 4
Dynamic bind:
In Child: 1 2
请按任意键继续. . .


第一个没有问题,子类指针调用子类函数,输出的结果也是子类给出的默认参数。但是,第二个问题就大了,我们明明触发了多态,但是,输出的结果竟然是基类给出的那两个默认参数的值!!!

为什么会这样?因为为了效率,函数的默认参数是使用静态绑定的,换句话说,不管你有没有多态,我只关心你用什么指针来调,基类指针就调用基类的默认参数,子类指针就给出子类的默认参数。而不像我们多态那样,会发生动态绑定,可以用基类指针调用子类函数。而我们在一个动态绑定的函数中使用了静态绑定的参数,结果肯定是不对的!

所以,正如《Effective C++》中所说:“绝不重新定义继承而来的缺省参数”!

发布了52 篇原创文章 · 获赞 86 · 访问量 42万+

猜你喜欢

转载自blog.csdn.net/dianxin113/article/details/78084612