在开发比较大型的C++项目的时候,这样一些场景你或许会遇到:1.维护别人写的代码;2.老板要你在加个功能;3.项目需要持续发布,功能在不断添加;等等,很多时候,我们可能需要对一些类原有函数增加参数。此时,你很容易就能想到的办法就是重载一下,或者修改原函数。本文就来分享一下在实际开发中的切身体验。
直接改原函数
比如这样的简单栗子(栗子仅为说明思路):
class point{
public:
point(double x,double y);
~point();
draw();
private:
double x;
double y;
};
其中的draw方法在其最初的版本时,就是简单的将(x,y)坐标处绘制一个点,至于这个点长啥样不管。可是忽然有一天,讨厌的光头产品经理跑过来说,这点黑漆麻咖的,太特么难看了!他想给这个点上点色。但是有的地方呢,他又觉得黑漆漆的还蛮好看。于是乎,就把draw改成这样了:
//假定用一个32bit 16进制值来表示RGB。
draw(unsigned int color);
完了,一看代码,发现只有很少几个地方需要特定的颜色显示,大部分默认就好了,可是原来函数已经变了啊,没办法我只能吭呲吭呲的把所有调用的地方都改一遍,泥马,好都地方要改呀~
改着改着,一想C++这么牛逼的语言,一定还有其他的方案来应对类似的场景。一拍脑门,想起来,可以重载个函数嘛。
于是乎.......
重载draw
这么一想,原类就变成这样了:
class point{
public:
point(double x,double y);
~point();
draw();
draw(unsigned int color);
private:
double x;
double y;
};
哇,很爽!就几个调用的地方需要替换,啪啪一顿替换,提交上线,完事。端起杯子喝水,一边看着自己的代码,还有没有其他的办法呢?
重载虽然爽,但是功能如果不断迭代,重载原来越多,大部分修改都很小的改动,可是类却变的越来越胖了!而且重载的代码里大部分内容与原函数基本一致,仅仅添加了一个颜色指定!心里隐隐觉得好似这样整也不是很爽。突然间,脑瓜里想起好像C++可以支持默认参数这一说。于是乎,又一顿敲.....
修改原函数
又继续回到老路上,把原来的类一顿改,变成这个鸟样:
#define DEFAULT_COLOR (0x00FFFFFF)
class point{
public:
point(double x,double y);
~point();
draw(unsigned int color=DEFAULT_COLOR);
private:
double x;
double y;
};
好嘛,就几个地方需要改,就把对应的地方替换一下:
pt.draw(0xxxxxx);
//其他地方,啥也不用改
pt.draw();
0xxxxxx为光头产品经理想要的颜色,其他的需要显示原颜色的很多地方不动,编译运行,效果一样!想着这下可以了。那么什么是C++的默认参数呢?
何为函数默认参数?
C++函数默认参数,是指函数声明中提供的值,如果函数的调用者未提供带有默认值的参数值,则该值由编译器自动分配。
我不清楚C++的设计者设计默认参数是否是出于这样的应用场景考虑,但是个人认为默认参数确实在本文类似的场景中表现的比重载更为优雅。让类不会因为不断迭代变的因为一些简单没必要增加重载函数的时候大显身手。
那么使用默认参数,需要注意些什么呢?
- 默认参数不同于常量参数,因为常量参数不能更改,而默认参数可以根据需要覆盖。
- 调用函数为其提供值时,默认参数将被覆盖。如果调用者不给定参数,编译器将声明中的默认值传入调用的地方。
- 将默认值用于函数定义中的参数后,在相同作用域中该参数的所有后续参数都必须具有默认值。也可以说默认参数是从右到左分配的。例如,以下函数定义无效,因为默认变量z的后续参数不是默认变量。
int sum(int x, int y, int z=0, int w)
什么是相同作用域呢?比如这样也是可以的:
{
void f(int n, int k = 1);
void f(int n = 0, int k); // OK: k的默认值在前一个函数的声明中指定了
}
- 默认参数是在函数声明中指定的,因此在函数体实现的地方就不能还带着默认值,这样编译会报错!比如
point::draw(unsigned int color=DEFAULT_COLOR)
{
......
}
- 虚拟函数的重载不会从基类声明中获取默认参数,并且在调用虚拟函数时,将根据对象的静态类型来确定默认参数。比如:
struct Base {
virtual void f(int a = 7);
};
struct Derived : Base {
void f(int a) override;
};
void m() {
Derived d;
Base& b = d;
b.f(); // 正确: 调用 Derived::f(7)
d.f(); // 错误: 没有default
}
当然关于默认参数还有些更多的语言细节需要去挖掘,但是大体上掌握了就基本可以开始使用了。随着不断的熟悉使用,就能明白更多小细节。本文故事采样默认参数,并不是说使用默认参数,就不需要去使用重载了,只是根据不同的应用场景合理选择罢了。