昨天面试的时候,被问到函数重载时第三个参数为空,只有两个参数会不会调用三个参数的函数,我脱口而出就说不会,回来想想感觉好像不对,然后查了一下资料,发现真的错了
例子:int fun(int a,int b,int c=0)
fun(5,8)是可以调用上面那个函数的,但如果又存在一个函数int fun(int a,int b)
则,fun(5,8)因不知道调用哪个会出错。
详解如下:
一.形参&实参
形参和实参,虽然用了这么久了,不过概念上还是有点纠结的。这里简单总结一下:形参是说明参数类型的,实参就是函数实际操作的对象,我们定义一个函数的时候,写的那个是形参,我们调用函数的时候,给如的参数就是实参。
最近在百度知道上看到了一个关于形参实参最精辟的解释,无耻的引用一下:
比如说进女厕所,那就是女人才能进去 ,那么女人就是进女厕所这个操作的形参,林黛玉进去了,杨贵妃进去了,林黛玉,杨贵妃这些就是实参,李隆基要进的话那就类型不符
二.简单使用
C++函数支持默认参数,这是一个很方便的特性。我们在函数声明或者定义的时候,给函数的参数设置一个默认值,当调用时如果不给参数或者给出一部分参数,那么就使用函数设定的默认参数值。先看一个例子:
-
-
-
- #include "stdafx.h"
- #include <iostream>
- using namespace std;
-
- void DefaultArguTest(int arg1, int arg2 = 2, int arg3 = 3)
- {
- cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;
- }
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
-
- cout<<"No Default argu:"<<endl;
- DefaultArguTest(1,1,1);
-
- cout<<"Default argu3:"<<endl;
- DefaultArguTest(1,1);
-
- cout<<"Default argu2,3:"<<endl;
- DefaultArguTest(1);
-
- system("pause");
- return 0;
- }
结果:
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.默认值可以是全局变量、全局常量,
甚至是一个函数。但不可以是局部变量。因为默认参数的调用是在编译时确定的,而局部变量位置与默认值在编译时无法确定。
四.默认参数和函数重载的冲突
默认参数和函数重载一起使用会导致冲突,看下面的例子:
-
-
-
- #include "stdafx.h"
- #include <iostream>
- using namespace std;
-
-
- void DefaultArguTest(int arg1 = 1, int arg2 = 2, int arg3 = 3);
-
- void DefaultArguTest();
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
- DefaultArguTest();
-
- system("pause");
- return 0;
- }
-
-
- void DefaultArguTest(int arg1, int arg2, int arg3)
- {
- cout<<arg1<<" "<<arg2<<" "<<arg3<<endl;
- }
错误如下:
error C2668: “DefaultArguTest”: 对重载函数的调用不明确
1> 可能是“void DefaultArguTest(void)”
1> 或 “void DefaultArguTest(int,int,int)”
对于当我们不给参数的时候,默认的DefaultArguTest和无参数的DefaultArguTest都可能被调用,所以就造成了调用不明确的错误。
仔细想一下,为什么C++的默认构造函数在我们自己定义了构造函数就自动不生成了呢?
个人感觉,有可能是害怕我们自己定义构造函数时,如果加上默认参数,那么就和编译器为我们提供的默认构造函数冲突了,为了防止这种隐患,索性如果自己写了构造函数,那就不生成默认构造函数了。
五.覆写函数时不要更换默认参数
如果我没记错的话这是《Effectice C++》中的一条,我们在覆写函数的时候,绝对不能修改它的默认参数,因为这会导致一个非常难发现的BUG!正因为如此,我们如果在VS(带VA插件的,本人猜测这个是VA插件加入的)中覆写带有默认参数的成员函数时,它会默认的将默认参数给出,以注释的形式给出提醒:
-
-
-
- #include "stdafx.h"
- #include <iostream>
- #include <string>
- using namespace std;
-
- class Base
- {
- public:
- virtual void Print(int i = 1, int j = 2)
- {
- cout<<"In base: "<<i<<" "<<j<<endl;
- }
- };
-
- class Child : public Base
- {
-
- void Print(int i , int j )
- {
- cout<<"In Child: "<<i<<" "<<j<<endl;
- }
- };
-
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- Base* base = new Child();
- base->Print();
-
-
- system("pause");
- return 0;
- }
结果:
In Child: 1 2
请按任意键继续. . .
但是,如果我们不信邪,偏偏要给子类加一个不同的默认参数,结果就会大大出乎我们的意料:
-
-
-
- #include "stdafx.h"
- #include <iostream>
- #include <string>
- using namespace std;
-
- class Base
- {
- public:
- virtual void Print(int i = 1, int j = 2)
- {
- cout<<"In base: "<<i<<" "<<j<<endl;
- }
- };
-
- class Child : public Base
- {
- public:
-
- void Print(int i = 3, int j = 4 )
- {
- cout<<"In Child: "<<i<<" "<<j<<endl;
- }
- };
-
-
-
- int _tmain(int argc, _TCHAR* argv[])
- {
-
- cout<<"Static bind:"<<endl;
- Child* child = new Child();
- child->Print();
-
-
- cout<<"Dynamic bind:"<<endl;
- Base* base = new Child();
- base->Print();
-
-
- system("pause");
- return 0;
- }
结果:
Static bind:
In Child: 3 4
Dynamic bind:
In Child: 1 2
请按任意键继续. . .
第一个没有问题,子类指针调用子类函数,输出的结果也是子类给出的默认参数。但是,第二个问题就大了,我们明明触发了多态,但是,输出的结果竟然是基类给出的那两个默认参数的值!!!
为什么会这样?因为为了效率,函数的默认参数是使用静态绑定的,换句话说,不管你有没有多态,我只关心你用什么指针来调,基类指针就调用基类的默认参数,子类指针就给出子类的默认参数。而不像我们多态那样,会发生动态绑定,可以用基类指针调用子类函数。而我们在一个动态绑定的函数中使用了静态绑定的参数,结果肯定是不对的!
所以,正如《Effective C++》中所说:“绝不重新定义继承而来的缺省参数”!