一、介绍
我们引进重载函数之后不但可以省去我们为实现同一个功能的函数去很多个名字的麻烦,而且可以方便我们实例化不同的对象,在C++中还可以通过重新定义运算符(也就是我之前写过的运算符的重载),使它能够用于特定类的对象执行特定的功能,这增强了C++语言的扩充能力。
二、什么是重载
函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组
函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名
的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
这样说可能不是很好理解,我们通过一个例子来解释下:
#include<iostream>
#include<string>
using namespace std;
void print(int a)
{
cout << "printf a int: " << endl;
}
void print(string s)
{
cout << "printf a string: " << s << endl;
}
void print(char ch)
{
cout << "printf a char: " << ch << endl;
}
int main()
{
print(1);
print("ni hao");
print('a');
return 0;
}
我通过定义了一个相同的函数名,在实参不同的时候,分别调用了想进行输出的函数,这样便实现了函数的重载。
三、编译器怎么解决重载问题
既然重载机制对我们这么重要,那么在编译器中,它是怎么解决命名冲突的这个问题?
就按照上述的例子来讲:
我们写的函数在发生编译之后,重载函数的名字其实都不再是print,这样就不存在什么
命名冲突的问题,那么它们的名字变成了什么?
void print(int a)-->_Z5printi
void print(string s)--> _Z5printSs
void print(char ch)--> _Z5printc
这样编译器变解决了命名冲突的问题,那么编译器又是如何实现这样的变名方法,即如何
将一个函数名映射到一个新的标识。我们可以这样理解Z后面的数字代表返回值类型,之后
是函数名,最后是参数列表,具体是怎么对应的我们不去细究,那么这样对应关系便是:
“返回类型+函数名+参数列表”。
虽然我还没有把编辑器解决这个问题的全部情况阐释清楚,但是我的目的只是为了让大家
了解编译器是怎么来处理命名冲突的这个问题,我上面考虑的函数是重载全局函数,如果
你在一个类中进行函数重载,那么便要考虑函数重载的重要限定——作用域。具体是怎样
进行的,我不做说明了。
四、重载函数的调用匹配
现在已经解决了重载函数命名冲突的问题,在定义完重载函数之后,用函数名调用的时候
是如何去解析的?为了估计哪个重载函数最适合,需要依次按照下列规则来判断:
精确匹配:参数匹配而不做转换,或者只是做微不足道的转换,如数组名到指针、函数名
到指向函数的指针、T到const T;
提升匹配:即整数提升(如bool 到 int、char到int、short 到int),float到double
使用标准转换匹配:如int 到double、double到int、double到long double、Derived*到
Base*、T*到void*、int到unsigned int;
使用用户自定义匹配;
使用省略号匹配:类似printf中省略号参数
如果在最高层有多个匹配函数找到,调用将被拒绝(因为有歧义、模凌两可)。
我们同样通过例子来说明:
void print(int);
void print(const char*);
void print(double);
void print(long);
void print(char);
void h(char c,int i,short s, float f)
{
print(c);//精确匹配,调用print(char)
print(i);//精确匹配,调用print(int)
print(s);//整数提升,调用print(int)
print(f);//float到double的提升,调用print(double)
print('a');//精确匹配,调用print(char)
print(49);//精确匹配,调用print(int)
print(0);//精确匹配,调用print(int)
print("a");//精确匹配,调用print(const char*)
}
如果我们定义太少或太多的重载函数,都有可能导致模凌两可,看下面的一个例子:
void f1(char);
void f1(long);
void f2(char*);
void f2(int*);
void k(int i)
{
f1(i);//调用f1(char)? f1(long)?
f2(0);//调用f2(char*)?f2(int*)?
}
这时侯编译器就会报错,将错误抛给用户自己来处理:通过显示类型转换来调用等等。上面的例子只是一个参数的情况,下面我们再来看一个两个参数的情况:
int pow(int ,int);
double pow(double,double);
void g()
{
double d=pow(2.0,2)//调用pow(int(2.0),2)? pow(2.0,double(2))?
}
以上两个例子编译器都会出现重载二义性的错误,所以大家看到这个error的时候,要回去找找自己那里定义的不合适。
五、重载函数的注意事项
1、函数的形参必须不同,或者个数不同,或者类型不同,不能够只依靠函数的返回值类
型不同或形参变量名不同来实现函数重载。
2、不要将不同功能的函数定义为重载函数,以免出现对调用结果的误解。
3、对于我上面说的重载二义性我还有点补充,重载函数与默认参数重叠导致的重载二义
性的error。
看个例子就明白了:
<span style="font-size:18px;">func(int); //重载函数1,只有1个参数,无默认参数
func(int, int =4); //重载函数2,有2个参数,有1个默认参数
func(int a=3, int b=4, int c=6); //重载函数3,有3个参数,有3个默认参数
fucn(float a=3.0, float b=4.0 float c=5.0); //重载函数4,有3个参数,有3个默认参数
fucn(float a=3.0, float b=4.0 float c=5.0 float d=7.9 ); //重载函数5,有4个参数,有4个默认参数
func(2); //可调用前3个函数,出现二义性
func(2.0); //可调用后2个函数,出现二义性
</span>
所以当重载函数与默认参数共同使用时,一定要注意出现二义性问题。