函数重载在c++中是一个简单而又关键的知识点,最近听了唐老师的课程,总结下文。
1. 什么是函数重载
所谓重载(Overload),就是同一标识符在不同的上下文有不同的意义。如“洗”和不同的词汇搭配后,“洗”字就有不同的含义: 洗衣服、洗脸、洗脑;如“play”和不同的单词搭配后,“play”有不同的含义: play chess、play piano、play basketball。重载的意义在于扩展已经存在的功能。
重载体现在函数中,即用同一个函数名定义不同的函数,当函数名和不同的参数搭配时的含义不同。
int func(char x){
return x;
}
int func(int a, int b){
return a + b;
}
int func(const char* s){
return strlen(s);
}
函数重载至少满足下面的一个条件:
(1) 函数参数不同
(2) 参数类型不同
(3) 参数顺序不同
2. 当函数默认参数遇上函数重载
int func(int a, int b, int c = 0)
{
return a * b * c;
}
int func(int a, int b)
{
return a + b;
}
int main()
{
func(1, 2); //调用哪个函数
return 0;
}
编译不过,编译器并不知道它要调用哪一个函数:
这样说到编译器调用重载函数的准则:
(1) 将所有同名函数作为候选者
(2) 尝试寻找可行的候选函数:精确匹配实参、通过默认参数匹配实参、通过默认类型转换匹配实参
出现匹配失败,原因有:
(1) 最终寻找到的候选函数不唯一,出现二义性
(2) 无法匹配所有候选者,函数未定义
3. 函数重载遇上函数指针
typedef int (*func_type)(int, int);
int func1(int a, int b){ //int(int, int)
return a + b;
}
int func2(char* str){ //int(chr* )
return strlen(str);
}
int main(void)
{
func_type p = func1;
printf("%d\n", p(1, 5)); //p调用的是哪一个函数
return 0;
}
函数指针func_type指向的是int(int, int)类型函数的地址,所以在main函数中将重载函数地址赋值给func_type类型的p时,自然是调用func1(int a, int b)。
程序运行结果:
函数重载遇上函数指针时,即将重载函数名赋值给函数指针时,遵循以下规则来匹配对应的重载函数:
(1) 根据函数重载规则挑选与函数指针参数列表一致的候选函数
(2) 严格匹配候选者函数类型与函数指针的函数类型,强调,这里还包括函数的返回值
在函数重载规则中,判断是否为重载函数根本不涉及其函数返回值,但是在函数指针与重载函数的匹配问题上,就需要匹配函数的返回值。c++是强类型的高级语言,在涉及到指针编程时会对类型更加严格限要求。
typedef void (*func_type)(int, int); //返回值为int型改为void,因此函数编译不过
int func1(int a, int b){ //int(int, int)
return a + b;
}
int func2(char* str){ //int(chr* )
return strlen(str);
}
int main(void)
{
func_type p = func1;
printf("%d\n", p(1, 5)); //p匹配不到重载函数
return 0;
}
将函数指针的返回类型改为void后编译不过,那改成double呢?int和double类型数据进行计算时,int会默认转换为double,但是一样编译不过,可见c++对类型匹配的严格要求。
4. 重载函数的特点
(1) 重载函数在本质上是相互独立的不同函数
(2) 重载函数的函数类型不同
(3) 函数的返回值不能作为函数重载的依据,函数的重载是由函数名和参数列表决定的(所以不能通过函数名直接得到重载函数的入口地址,重载函数的n个函数,其函数名是相同的)
(4) 函数重载必须发生在同一作用域中
重载函数在本质上是相互独立的不同函数,来验证看看:
如下程序,在window操作系统的visual studio2010编译执行,
typedef int (*func_type1)(int, int);
typedef int (*func_type2)(int, int, int);
int add(int a, int b) //函数类型int(int, int)
{
return a + b;
}
int add(int a, int b, int c) //函数类型是int(int, int, int)
{
return a + b + c;
}
int main(void)
{
printf("address = %p, add(1, 2) = %d\n", (func_type1)add, add(1, 2));
printf("address = %p, add(1, 2, 3) = %d\n", (func_type2)add, add(1, 2, 3));
//下面两句效果一致
//printf("address = %p, add(1, 2) = %d\n", (int(*)(int, int))add, add(1, 2));
//printf("address = %p, add(1, 2, 3) = %d\n", (int(*)(int, int, int))add, add(1, 2, 3));
getchar();
return 0;
}
两个重载函数的地址不相同,证明两个函数在编译器看来是两个各自独立的函数。
运行结果:
两个重载函数的地址不相同,证明两个函数在编译器看来是两个各自独立的函数。
在程序源码所在目录的Debug目录下,有.obj文件,这个文件是编译器编译产生中间文件
通过visual studio2010命令行工具
打开它,可以看见编译时产生的符号表(SYMBOLS)
可以看到关键:
00E 00000000 SECT3 notype () External | ?add@@YAHHH@Z (int __cdecl add(int,int))
01F 00000000 SECT7 notype () External | ?add@@YAHHHH@Z (int __cdecl add(int,int,int))
“| ?”后面的相当于c语言中的注释,可见,两个add函数已经被编译成add@@YAHHH@Z函数和add@@YAHHHH@Z函数了,同样能够证明重载函数经过编译器编译之后得到的是两个独立的函数。
5. 类中函数的重载
前面讲到的都是全局函数,c++类中的成员函数依旧可以进行重载。类中的成员函数无非就3中:构造函数、普通成员函数和静态成员函数。重载函数必须发生在同一作用域中,所以说,
(1) 全局函数和成员函数不够构成重载关系
(2) 全局函数不能和类的静态成员函数构成重载关系。(类的静态成员函数的访问需要指定类名或者对象名)
除此之外,类中的成员的重载遵循的规则,跟全局函数间重载规则一致。