必备技能6.7:函数重载
在本小节中,我们将学习到C++中一个令人兴奋的特性:函数重载。在C++中,只要函数的参数声明不同,两个或者多个函数是可以使用同一个名称的。这时,我们就说这两或者多个函数被重载了。这样的过程就是函数重载。函数重载是C++支持多态性的一种方式。
通常情况下,为了重载一个函数,我们只需要声明一个函数的另外一种形式即可,剩余的事情有编译器来处理。函数重载必须遵循一个重要的原则:重载函数的参数类型或者参数数量不能相同。它们要么是参数的个数不相同,要么是参数的类型不相同。(由于函数的返回值不能提供足够的信息以便区分应该调用哪个版本的函数,所以只有返回值不同的函数不能重载。)当然,重载的函数是可以在返回值上不相同的。当我们调用重载的函数的时候,参数能够匹配上的那个版本的函数会被调用。
我们以下面的这个短小的程序开始学习函数重载:
//重载一个函数三次 #include <iostream> using namespace std; //针对每一次的重载,都需要声明函数的原型 void f(int i ); //整型参数 void f(int i, int j ); //需要两个整型参数 void f(double k); //浮点型参数 int main() { f(10); //调用函数f(int) f(10,20); //调用函数f(int,int) f(12.23); //调用函数f(double) return 0; } //下面是三个f()函数的不同的实现 void f(int i) { cout << "In f(int), i is " << i << '\n'; } void f(int i, int j) { cout << "In f(int), i is " << i; cout << ", j is " << j << '\n'; } void f(double k ) { cout << "In f(double), k is " << k << '\n'; }
In f(int), i is 10
In f(int), i is 10, j is 20
In f(double), k is 12.23
从中可以看出,函数f()被重载了三次。第一次,需要一个整型数作为参数;第二次需要两个整型数作为参数;第三次需要一个double类型的参数。因为三个版本所需要的参数的列表是不一样的,编译器就能够根据调用时传入参数的类型来决定应该调用哪个版本的函数。为了能更好地理解重载的价值,我们参考函数neg(),用来返回参数的相反数。例如,当调用时传入参数-10,neg()就返回10;当传入参数9,neg()就返回-9。在没有函数重载的情况下,如果我们想创建求相反数的函数来分别实现对int、double和long三种类型的参数求相反数,那么我们就需要三个不同的函数,每个函数的名称都不一样,例如,ineg(),lneg()和fneg()。然而,通过使用函数重载,我们就只需要一个函数名称neg()即可,用它代表所有能返回参数相反数的函数。因此,重载支持了多态性概念“一个接口,多种实现”。下面的程序展示了这一点:
//创建函数neg()的不同版本 #include <iostream> using namespace std; int neg(int n); //neg() for int double neg(double n ); //neg() for double long neg(long n ); //neg() for long int main() { cout << "neg(-10): " << neg(-10) << "\n"; cout << "neg(9L): " << neg(9L) << "\n"; cout << "neg(11.23): " << neg(11.23) << "\n"; return 0; } //neg() for int int neg(int n ) { return -n; } //neg() for double double neg(double n ) { return -n; } //neg() for long long neg(long n ) { return -n; }
上面程序的输出结果如下:
neg(-10): 10
neg(9L): -9
neg(11.23): -11.23
在上面的程序中,我们创建了三个相似但却不同的函数,它们的名称都是neg(),每一个都是返回其参数的相反数。在调用该函数的时候,编译器能根据参数的类型确定应该调用哪个函数。
重载的价值就在于它使得我们可以使用同一个名称来访问一组相关的函数。因此,名称neg就代表了函数完成的通用功能。由编译器根据调用时传入参数的情况来确定应该调用哪个版本的函数。我们,也就是程序员只需要记住这个通用的名称即可。因此,通过使用多态性,我们需要记住的东西就由三个变成了一个。尽管,上面的程序十分简单,但是如果我们将这个概念扩展开来,我们就能看到重载是如何帮助我们管理更大型程序的。
函数重载的另外一个好处:就是一些功能针对不同的数据类型其实现不同,但是确可以使用相同的函数名称。例如,假设我们想实现计算两个值中较小值的功能。这是针对不同的数据类型,其实现有可能不同,但是我们可以使用同一个函数名称min()。当我们比较的是两个整型数的时候,函数min()返回其中数值较小的那个。当我们比较的是两个字符的时候,min()函数就返回最先出现在字母表中的那个字符,不考虑大小写。在ASCII码序列中,大写字母的表示之要比小写字母的表示值小32。因此,当按照字母顺序排序的时候,忽略大小写是十分有用的。当我们比较的是两个指针的时候,我们可以让min()函数比较两个指针指向的值,并返回指向较小值的那个指针。下面的程序就实现了上述的三个版本的min()函数:
//min()函数的不同实现 #include <iostream> using namespace std; int min(int a, int b); //整型参数的min()函数 char min(char a, char b); //字符型参数的min()函数 int* min(int* a, int* b); //指针型参数的min()函数 int main() { int i = 10, j =22; cout << "min('X','a'): " << min('X','a') << "\n"; cout << "min(9, 3): " << min(9,3) << "\n"; cout << "*min(&i,&j): " << *min(&i, &j) << "\n"; return 0; } //整型参数的min()函数,返回较小的值 int min(int a, int b) { if ( a < b) return a; else return b; } //字符型参数的min()函数,比较字母的时候忽略大小写 char min( char a, char b) { if (tolower(a) < tolower(b) ) return a; else return b; } //指针型参数的min()函数,比较指针指向值的大小,返回指向较小值的指针 int * min(int* a, int *b) { if ( *a < *b ) return a; else return b; }
上面程序的输出如下:
min('X','a'): a
min(9, 3): 3
*min(&i,&j): 10
当我们重载函数的时候,函数的每个版本都可以实现任何我们想完成的功能。也就是说,没有规则要求我们重载的函数必须是相关联的。然而,从格式上来讲,函数重载就暗含着这些函数是相关联的。因此,当函数是不相关的时候,我们就不应该使用函数重载,而让它们使用相同的函数名称。例如,我们可以使用sqrt()这个名称来创建两个函数,一个返回整型数的平方,另一个则返回一个double类型数的平方根。这两个功能显然是不同的,这样的用法就违背了函数重载的本意。(实际上,这种编程方式是一种极其不好的编程风格!)实践中,我们只应该对那些紧密相关的函数进行重载。
自动类型转换和重载
回忆一下在第二篇中,我们学习到C++提供了一定的自动类型转换功能。这种自动类型转换同样可用于重载函数的参数。例如下面的程序:
/* 自动类型转换可以影响函数重载的结果 */ #include <iostream> using namespace std; void f(int x); void f(double x); int main() { int i = 10; double d = 10.1; short s =99; float r = 11.5F; f(i); //调用函数f(int) f(d); //调用函数f(double) f(s); //调用函数f(int) 自动类型转换 f(r); //调用函数f(double) 自动类型转换 return 0; } void f( int x) { cout <<"Inside f(int): " << x << "/n"; } void f( double x) { cout <<"Inside f(double): " << x << "/n"; }
Inside f(int): 10
Inside f(double): 10.1
Inside f(int): 99
Inside f(double): 11.5
在上面的例子中,我们只是定义了两个版本的f()函数:一个需要一个int类型的参数,另一个需要的是double类型的参数。然而,我们还是可以给函数f()传入一个short类型或者float类型的参数。这里,C++自动地把short转换为int类型,因此调用的是函数f(int);而把float转换为double类型,因此调用的是函数f(double)。
有一点必须明确,那就是自动类型转换只有在实参和函数的形参不能完全匹配的情况下才进行。例如,我们针对上面的程序增加一个需要short类型参数的的f()函数:
/* 自动类型转换可以影响函数重载的结果 */ #include <iostream> using namespace std; void f(int x); void f(short x); //增加一个需要short类型参数的函数f() void f(double x); int main() { int i = 10; double d = 10.1; short s =99; float r = 11.4F; f(i); //调用函数f(int) f(d); //调用函数f(double) f(s); //调用函数f(short) f(r); //调用函数f(double) 自动类型转换 return 0; } void f( int x) { cout <<"Inside f(int): " << x << "/n"; } void f( short x) { cout <<"Inside f(short): " << x << "/n"; } void f( double x) { cout <<"Inside f(double): " << x << "/n"; }现在运行程序,输出如下:
Inside f(int): 10
Inside f(double): 10.1
Inside f(short): 99
Inside f(double): 11.4
在这个版本中,由于存在一个函数f()它需要一个short类型的参数,所以当我们调用函数f()时,如果传入的参数是short类型,就会调用需要short类型参数的这个函数f(short),而不会进行自动类型转换。
练习:
1. 进行函数重载必须满足什么条件?
2. 为什么重载函数必须完成的是相关的功能?
3. 函数的返回值是否参数函数重载的解析?