C++那些细节--函数的默认参数

来源:互联网 发布:淘宝店怎么样提高销量 编辑:程序博客网 时间:2024/06/05 16:59

关于C++函数默认参数的问题,来一个整理。


一.形参&实参

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

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

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

二.简单使用

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

// C++Test.cpp : 定义控制台应用程序的入口点。//#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[]){//第2,3个参数给出了,则使用参数的值cout<<"No Default argu:"<<endl;DefaultArguTest(1,1,1);//第3个参数没给出,则使用默认值cout<<"Default argu3:"<<endl;DefaultArguTest(1,1);//第2,3个参数都没给出,使用默认值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.默认值可以是全局变量、全局常量,甚至是一个函数。但不可以是局部变量。因为默认参数的调用是在编译时确定的,而局部变量位置与默认值在编译时无法确定。


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

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

// C++Test.cpp : 定义控制台应用程序的入口点。//#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插件加入的)中覆写带有默认参数的成员函数时,它会默认的将默认参数给出,以注释的形式给出提醒:

// C++Test.cpp : 定义控制台应用程序的入口点。//#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{//我们覆写带有默认参数的函数,VA插件给出了提醒,这两个值都是有默认参数的void Print(int i /* = 1 */, int j /* = 2 */){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
请按任意键继续. . .


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

// C++Test.cpp : 定义控制台应用程序的入口点。//#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++》中所说:“绝不重新定义继承而来的缺省参数”!







0 0
原创粉丝点击