第二十一篇 -- 研究下函数(四) —— 函数重载
在C++程序中不允许有相同的函数出现,否则调用时无法区分到底使用哪一个。区分两个函数靠的不仅是函数名,还有函数的参数列表。如果多个函数拥有相同的函数名,但参数列表不同,则称为函数重载,例如:
int function(); int function(int); int function(double); int function(int, double);
虽然上述4个函数的名字都是“function”,但参数列表不同,所以可以共存于一个程序中。只是在调用时,需要传入不同的实参,以便调用所需的目标函数。
当心:函数的返回类型不能用来区别函数。如果两个函数仅返回类型不同,则第二个出现的函数会被认为是对第一个函数的错误重复。
为什么需要函数重载?
其实重载早在C语言中就已经存在了,例如各种算术运算符。整数和浮点数在内存中的表示方式是不一样的,但在进行各种算数运算时用的运算符却是一样的,例如:
1 + 2; 1.1 + 2.2;
上述表达式中就应用了重载的加号运算符。如果不使用重载,那么对于整数的加法和浮点数的加法就要采取两种完全不同的运算符。整数仍然用“+”,而浮点数或许可以用“?”表示加法运算。这样做不仅使程序变得很复杂,也无助于解决问题。所以,只有重载才是解决问题的办法。
C++语言将重载进一步扩展,不仅运算符可以重载,而且函数也可以重载。这样,一个函数名就可以应用到不同的参数列表上,从而对不同的参数列表采取不同的操作。
什么时候需要重载函数?
定义一组函数,其目的相似,但是参数(类型或数量)不同,此时就应当使用函数重载。因为目的相似,所以可以用同一个函数名来标识。或许读者会想到给函数名加上不同的前缀或后缀来区别函数,从而避免重载,但这样做无疑会使程序变得复杂。
说明:“函数重载”重载的其实是函数名,因此准确的说法是“函数名重载”。不过“函数重载”已经收到广泛的认同。
下面的例子用于普安端某个数据是不是0.对于整数,显然只要判断是否与0相等即可;对于浮点数,则不能直接比较,通常的做法是判断其绝对值是否小于某个很小的数,例如1e-6;对于字符,则应当比较其是否等于字符0,而不是ASCII码0.显然,这些判断目的类似,但目标数据不同,所以应当使用函数重载。
#include "pch.h" #include <iostream> #include "method.h" using namespace std; bool isZero(int x); bool isZero(double x); bool isZero(char x); int main() { cout << "Is zero?" << endl; cout << 1 << '\t' << isZero(1) << endl; cout << 0 << '\t' << isZero(0) << endl; cout << 1.0 << '\t' << isZero(1.0) << endl; cout << 1e-5 << '\t' << isZero(1e-5) << endl; cout << 1e-7 << '\t' << isZero(1e-7) << endl; cout << 'a' << '\t' << isZero('a') << endl; cout << '0' << '\t' << isZero('0') << endl; return 0; } bool isZero(int x) { return 0 == x; } bool isZero(double x) { return -1e-6 < x && x < 1e-6; } bool isZero(char x) { return '0' == x; }
从判断是否为0的角度来讲,对于任何数据的判断都只用isZero这个函数名就够了。但其内部操作不同,而且处理的参数也不同,所以应当使用重载。
函数重载解析
如果一个函数名被重载,那么在调用时选择函数的过程就是重载解析。例如下面的重载函数:
void f(int, int); void f(float, float); int main(){ ... f(t1, t2); return 0; }
函数重载解析过程将决定:用实参t1和t2能否调用函数f(两个重载函数中的一个),用t1和t2调用是否存在二义性,到底调用的是f(int, int)还是f(float, float)。函数重载解析的过程有如下3个步骤:
step1 确定实参列表的属性,确定候选函数的集合。
step2 根据实参的个数和类型确定合适的函数。
step3 选择精确匹配的函数。
void f(); void f(int); void f(double, double = 3.4); void f(char*, char*); int main(){ f(7.6); return 0; }
函数重载解析的第一步是确定候选函数。候选函数是被调用函数同名的函数,并且在调用点上其声明可见。在本例中有4个候选函数:f(), f(int), f(double, double)以及f*(char*, char*)。
函数重载解析的第一步还要确定实参的属性,即其数目和类型。在本例中实参表由一个double型的实参构成。
第二步根据实参属性从候选函数中选择合适的函数。合适的函数的参数个数应与实参列表中的参数数目相同,并且实参与该函数形参的类型之间必须存在转换。在这个例子中有两个合适的函数:f(int)和f(double, double)。f(int)只有一个参数,而且存在从实参类型double 到形参类型int之间的转换。f(double, double)也是一个合适的函数,因为其第二个参数给出了默认值,而第一个形参类型是double,与实参类型精确匹配。
提示:如果函数重载解析过程的第二步没有找到任何合适的函数,则函数调用就是错误的。
函数重载解析的第三步是选择精确匹配的函数,为了选择这个函数,从实参类型到相应可行函数参数所用的转换都要划分等级。最精确匹配的函数应符合以下的标准:
1. 应用在实参上的转换,不比调用其他可行函数所需的转换差。
2. 在某些实参上的转换,要比其他可行函数对该参数的转换好。
当考虑可行函数f(int)时,应用的转换是一个标准转换:将double型的实参转换成int型。当考虑到可行函数f(double)时,实参的类型double与相应的参数精确匹配,因此该调用的最佳可行函数是f(double, double)。
提示:如果函数重载解析的第三步没有找到最精确匹配的函数,则该函数调用是有二义的,即没有找到一个比其他可行函数都好的函数。