Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1798916
  • 博文数量: 600
  • 博客积分: 10581
  • 博客等级: 上将
  • 技术积分: 6205
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-06 10:13
文章分类
文章存档

2016年(2)

2015年(9)

2014年(8)

2013年(5)

2012年(8)

2011年(36)

2010年(34)

2009年(451)

2008年(47)

分类: C/C++

2009-09-19 13:26:42

在函数重载和设定参数缺省值间慎重选择

会对函数重载和设定参数缺省值产生混淆的原因在于,它们都允许一个函数以多种方式被调用:

void f();	// f被重载
void f(int x);
f();	// 调用f()
f(10);	// 调用f(int)
void g(int x = 0);	// g 有一个
		// 缺省参数值
g();	// 调用g(0)
g(10);	// 调用g(10)

那么,什么时候该用哪种方法呢?

答案取决于另外两个问题。第一,确实有那么一个值可以作为缺省吗?第二,要用到多少种算法?一般来说,如果可以选择一个合适的缺省值并且只是用到一种算法,就使用缺省参数(参见条款38)。否则,就使用函数重载。

下面是一个最多可以计算五个int的最大值的函数。这个函数使用了——深呼一口气,看清楚啦——std::numeric_limits::min(),作为缺省参数值。等会儿再进一步介绍这个值,这里先给出函数的代码:

int max(int a,
	int b = std::numeric_limits::min(),
	int c = std::numeric_limits::min(),
	int d = std::numeric_limits::min(),
	int e = std::numeric_limits::min())
{
 int temp = a > b ? a : b;
 temp = temp > c ? temp : c;
 temp = temp > d ? temp : d;
 return temp > e ? temp : e;
}

现在可以放松了。std::numeric_limits::min()是c++标准库用一种特有的新方法所表示的一个在c里已经定义了的东西,即c在中定义的int_min宏所表示的那个东西——处理你的c++原代码的编译器所产生的int的最小可能值。是的,它的句法背离了c所具有的简洁,但在那些冒号以及其它奇怪的句法背后,是有道理可循的。

假设想写一个函数模板,其参数为固定数字类型,模板产生的函数可以打印用“实例化类型”表示的最小值。这个模板可以这么写:

template
void printminimumvalue()
{
 cout << 表示为t类型的最小值;
}

如果只是借助来写这个函数会觉得很困难,因为不知道t是什么,所以不知道该打印int_min还是dbl_min,或其它什么类型的值。

为避开这些困难,标准c++库(见条款49)在头文件 中定义了一个类模板numeric_limits,这个类模板本身也定义了一些静态成员函数。每个函数返回的是“实例化这个模板的类型”的信息。也就是说,numeric_limits中的函数返回的信息是关于类型int的,numeric_limits 中的函数返回的信息是关于类型double的。numeric_limits中有一个函数叫min,min返回可表示为“实例化类型”的最小值,所以numeric_limits::min()返回的是代表整数类型的最小值。

有了numeric_limits(和标准库中其它东西一样,numeric_limits存在于名字空间std中;numeric_limits本身在头文件中),写printminimumvalue就可以象下面这样容易:

template
void printminimumvalue()
{
 cout << std::numeric_limits::min();
}

采用基于numeric_limits的方法来表示“类型相关常量”看起来开销很大,其实不然。因为原代码的冗长的语句不会反映到生成的目标代码中。实际上,对numeric_limits的调用根本就不产生任何指令。想知道怎么回事,看看下面,这是numeric_limits::min的一个很简单的实现:

#include 
namespace std {
 inline int numeric_limits::min() throw ()
 { return int_min; }
}

因为此函数声明为inline,对它的调用会被函数体代替(见条款33)。它只是个int_min,也就是说,它本身仅仅是个简单的“实现时定义的常量”的#define。所以即使本条款开头的那个max函数看起来好象对每个缺省参数进行了函数调用,其实只不过是用了另一种聪明的方法来表示一个类型相关常量而已(本例中常量值为int_min)。象这样一些高效巧妙的应用在c++标准库里俯拾皆是,这可以参考条款49。

回到max函数上来:最关键的一点是,不管函数的调用者提供几个参数,max计算时采用的是相同(效率很低)的算法。在函数内部任何地方都不用在意哪些参数是“真”的,哪些是缺省值;而且,所选用的缺省值不可能影响到所采用的算法计算的正确性。这就是使用缺省参数值的方案可行的原因。

对很多函数来说,会找不到合适的缺省值。例如,假设想写一个函数来计算最多可达5个int的平均值。这里就不能用缺省参数,因为函数的结果取决于传入的参数的个数:如果传入3个值,就要将总数除以3;如果传入5个值,就要将总数除以5。另外,假如用户没有提供某个参数时,没有一个“神奇的数字”可以作为缺省值,因为所有可能的int都可以是有效参数。这种情况下就别无选择:必须重载函数:

double avg(int a);
double avg(int a, int b);
double avg(int a, int b, int c);
double avg(int a, int b, int c, int d);
double avg(int a, int b, int c, int d, int e);

另一种必须使用重载函数的情况是:想完成一项特殊的任务,但算法取决于给定的输入值。这种情况对于构造函数很常见:“缺省”构造函数是凭空(没有输入)构造一个对象,而拷贝构造函数是根据一个已存在的对象构造一个对象:

// 一个表示自然数的类
class natural {
public:
 natural(int initvalue);
 natural(const natural& rhs);
 
private:
 unsigned int value;
 
 void init(int initvalue);
 void error(const string& msg);
};

inline
void natural::init(int initvalue) { value = initvalue; }
natural::natural(int initvalue)
{
 if (initvalue > 0) init(initvalue);
 else error("illegal initial value");
}

inline natural::natural(const natural& x)
{ init(x.value); }

输入为int的构造函数必须执行错误检查,而拷贝构造函数不需要,所以需要两个不同的函数来实现,这就是重载。还请注意,两个函数都必须对新对象赋一个初值。这会导致在两个构造函数里出现重复代码,所以要写一个“包含有两个构造函数公共代码”的私有成员函数init来解决这个问题。这个方法——在重载函数中调用一个“为重载函数完成某些功能”的公共的底层函数——很值得牢记,因为它经常有用(见条款12)。

阅读(369) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~