《Visual C# 最佳实践》第四章 函数 (三):函数重载
时间:2011-01-22 来源:邹俊才
4.3函数重载
函数重载(Overload)为程序开发提供了极大的便利,尤其是对于我们这些类库的使用者——可以从复杂的类型转换中解放出来,只要以我们自己认为最方便的方式调用方法即可。本节主要从重载的语义角度来讨论与使用重载相关的一些基本条件。
4.3.1函数重载的概念
函数重载是指多个函数实现可以同时使用一个函数名,只是函数的参数表不同而已。打个比方,使用重载函数我们可以定义多个加法函数来求两个数之和。其中,一个函数实现两个int类型之和,另外一个实现两个float类型之和,再另外一个实现两个decimal之和。每种实现对应一个函数体,这些函数的名字相同,但是函数的参数类型不同,这就是函数重载的概念。
刚才我们说函数重载时多个函数使用同一个函数名。那么,C#语言的编译器就需要确定用户调用的是哪一个函数,即采用哪个函数实现。确定函数实现时,要求从函数参数的个数和类型上来区分。这就是说,进行函数重载时,要求同名函数在参数个数上不同,或者参数类型上不同。否则,将无法实现重载。
函数重载的格式如下:
void add(int a,int b)
{}
void add(int a,int b,int c)
{}
char add(char a,char b,char c)
{}
上述格式中,定义了3个add函数,第一个add()函数对应的是两个int类型求和的函数实现,而后边一个add()函数对应的是三个int类型求和的函数实现,最后一个add()函数对应的是三个char类型求和的函数实现,这便是函数的重载。
下面我们来看一个范例:
using System;
namespace Microsoft.Example
{
public class TestReturnValue
{
static int Max(int x, int y) //定义int版本的Max函数
{
int z;
z = x > y ? x : y; //比较x,y的大小
return z;
}
static double Max(double x, double y) //定义double版本的Max函数
{
double z;
z = x > y ? x : y; //比较x,y的大小
return z;
}
static void Main(string[] args)
{
int intX = 50; //定义一个int变量
int intY = 43; //定义一个int变量
Console.WriteLine("比较两个整数的大小:" + intX + "; " + intY);
int intResult = Max(intX, intY); //调用int版本的Max函数
Console.WriteLine("结果是:" + intResult);
double doubleX = 50.5; //定义一个double变量
double doubleY = 43.3; //定义一个double变量
Console.WriteLine("比较两个整数的大小:" + doubleX + "; " + doubleY);
double doubleResult = Max(doubleX, doubleY); //调用int版本的Max函数
Console.WriteLine("结果是:" + doubleResult);
}
}
}
上述代码中,第6行我们定义了一个int版本的Max函数,这个函数只接收两个int类型的参数,然后返回int类型的数据。第12行我们定义了一个double版本的Max函数,这个函数只接收两个double类型的参数,然后返回double类型的数据。这两个函数使用了Max的函数名字,但是,它们能共同存在,这就是函数重载。
最后的输出结果是:
比较两个整数的大小:50; 43
结果是:50
比较两个整数的大小:50.5; 43.3
结果是:50.5
4.3.2函数重载的优点
从上面的范例中,我们可以知道,虽然不同的重载在形式上是多个独立的函数,但在语义上它们代表的是同一个函数——准确地说,它们执行的是同样的操作。之所以函数要提供多个重载,其主要目的是方便调用者。具体地说来包括以下一些优点:
1、支持多种数据类型
前面关于Max函数的范例即是说明了这一点。我们要提供的是“取最大值”的功能,并不是“对整型数取绝对值”的功能,因此将输入参数限制为整型是不合适的。提供重载可以使得用户不必操心有关数据类型转换的工作,各个重载函数会负责针对不同的数据类型进行处理,也减少了记忆。
2、支持多种数据提供方式
这是比支持多种数据类型更为宽泛而方便的方式。Int类型与double类型之间可以直接转换,但很多情况却没有那么简单。例如XmlDocument类提供了Load方法用于装入一个XML文档,那么如何指定这个文档呢?如果这个文档还没有被打开,那么指定它的路径及文件名是最方便的。如果这个文档已经打开,那么也许可以通过一个Stream对象对其进行访问是最方便的。对于类库的使用者来说,这些可能都是存在的,如果XmlDocument强制使用一种方式,无疑会给用户造成不便,此时最好的办法就是提供一组重载:
XmlDocument.Load(string)
XmlDocument.Load(Stream)
这里的string、Stream之间是不存在类型转换关系的,重载函数要做的额外工作并不像之前那个最大值函数那么简单。但是对于函数的使用者来说,它们的意义是类似的。
3、为复杂的参数提供默认值,简化调用
这也是非常常见的情况。有些函数的功能非常强大,但其带来的负面影响就是函数调用者需要考虑太多的参数设置。事实上,大多数情况下,使用者真正关心的只是其中的一两个参数,剩余的参数都集中在某些相同的设置值上。如果能为这些高级特性提供合理的默认值的话,函数调用者的工作量就会大大降低。例如File的Create方法用于创建一个文件,它支持很多丰富的设置参数:
public void Create(
string path;
int buffersize;
FileOptions options;
FileSecurity fileSecurity;
}
这么多设置参数,我们第一次创建文件的时候肯定会不知所措,按我们的理解,只要指定文件地址就够了。就像下面这样:
public void Create(
string path;
}
事实上Create确实提供了这样一个简单版本的重载,确实在大多数情况下,用户只需要一些简单的功能,那些复杂的特性被使用的频率相对较低。如果每次都要调用者设置这么多参数的话,显然增加了很多不必要的劳动。如果提供一个简单版本的重载,为这些高级特性提供合理的默认值的话,可以在很大程度上使代码变得更加简洁有序。前面这个重载函数,或许就是通过下面的方式来实现的:
public void Create (string path)
{
Create (
path;
0
FileOptions.None
null
);
}
可以清楚地看到,如果没有这些重载来帮助设置默认参数值的话,用户需要多编写多少代码去完成一个本应该非常简单的操作。
作者心得:
从之前所述的重载语义中,我们不难发现,函数的各个重载之间的区别仅在于对入口参数进行的预处理,其核心功能都是一致的。如果每个重载都单独编写的话,必然会产生大量的冗余代码,不但增加了编写代码的工作,也给日后的修改维护造成极大的困难。既然提供重载只是为了处理不同的入口参数形式,那么各个重载函数的工作也就应该是对参数进行处理,核心功能应该只由一个函数实现,其他所有重载都调用这个函数。