C++ 基础系列——函数重载
1. 函数重载定义
如果同一作用域内的几个函数名字相同但形参列表不同,称为重载函数。
参数列表又叫参数签名,包括参数的类型、个数和顺序,只要有一个不同就叫做参数列表不同。
void swap1(int *a, int *b); //交换 int 变量的值
void swap2(float *a, float *b); //交换 float 变量的值
void swap3(char *a, char *b); //交换 char 变量的值
void swap4(bool *a, bool *b); //交换 bool 变量的值
2. C++ 如何实现函数重载
C++在编译时会根据参数列表对函数进行重命名,例如 void Swap(int a, int b)
会被重命名为 _Swap_int_int
,void Swap(float x, float y)
会被重命名为_Swap_float_float
。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。
不同的编译器有不同的重命名方式。函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。
3. C++函数重载过程中的二义性和类型转换
当形参和实参能够精确匹配时,情况会比较简单。但当形参和实参类型不匹配,涉及到类型转换时,就会变得比较复杂了。
//1 号函数
void func(char ch){
cout<<"#1"<<endl;
}
//2 号函数
void func(int n){
cout<<"#2"<<endl;
}
//3 号函数
void func(long m){
cout<<"#3"<<endl;
}
//4 号函数
void func(double f){
cout<<"#4"<<endl;
}
int main()
{
short s = 99;
float f = 81.6;
func('a'); //不需要类型转换,调用 func(char)
func(s); //将 short 转换成 int,调用 func(int)
func(49); //不需要类型转换,调用 func(int)
func(f); //将 float 转换成 double,调用 func(double)
}
这段代码很容易理解,但是如果将 2 号函数 int 参数版本的去掉,编译会出错。
大概的意思是:func(s)
和 func(49)
这两个函数发生调用错误,它们可以匹配三个重载函数中的任何一个,编译器不知道如何抉择。
C++ 标准规定,在进行重载决议时编译器应该按照下面的优先级顺序来处理实参的类型:
优先级 | 包含的内容 | 举例说明 |
---|---|---|
精确匹配 | 不做类型转换,直接匹配 | (暂无说明) |
只是做微不足道的转换 | 从数组名到数组指针、从函数名到指向函数的指针、从非 const 类型到const 类型。 | |
类型提升后匹配 | 整型提升 | 从 bool、char、short 提升为 int,或者从char16_t、char32_t、wchar_t 提升为 int、long、long long。 |
小数提升 | 从 float 提升为 double。 | |
使用自动类型转换后匹配 | 整型转换 | 从 char 到 long、short 到 long、int 到 short、long 到 char。 |
小数转换 | 从 double 到 float。 | |
整数和小数转换 | 从 int 到 double、short 到 float、float 到 int、double 到long。 | |
指针转换 | 从 int * 到 void *。 |
- 编译器匹配优先级由高到低,如果在同一个优先级找到多个合适的重载函数,就会发生二义性错误。
- 类型提升和类型转换不是一码事。类型提升是积极的,是为了更加高效地利用计算机硬件,不会导致数据丢失或精度降低;而类型转换是不得已而为之,不能保证数据的正确性,也不能保证应有的精度。
- 类型提升只有上表中列出的几种情况,其他情况都是类型转换。
4. 多个参数时的二义性
// 假设有一下几个函数原型
void func(int, int); // ①
void func(char, int, float); // ②
void func(char, long, double); // ③
函数调用
short n = 99;
func('@', n, 99);
func('@', n, 99.5);
函数原型 1 只有两个参数,而函数调用有三个参数,初次筛选时就会被过滤掉,接下来我们只讨论 2、3 函数原型:
- 第一个函数调用。如果只考虑第一个实参'@', ②、③ 都能够精确匹配,是平等的;如果只考虑第二个实参 n,对于②,需要把 short 提升为 int(类型提升),对于③,需要把 short 转换为 long(类型转换),类型提升的优先级高于类型转换,所以②胜出;如果只考虑第三个实参 99,②、③ 都要进行类型转换,没有哪一个能胜出,它们是平等的。
从整体上看,②、③在第一、三个实参的匹配中是平等的,但 ② 在第二个实参的匹配中胜出,所以 ② 成为被调用函数。- 再来看第二个函数调用。只考虑第一个实参时②、③是平等的,没有谁胜出;只考虑第二个实参时②胜出;只考虑第三个实参时,②需要类型转换,③能够精确匹配,精确匹配的优先级高于类型转换,所以③胜出。
从整体上看,②、③在第一个实参的匹配中是平等的,②在第二个实参的匹配中胜出,③在第三个实参的匹配中胜出,它们最终“打成了平手”,分不清孰优孰劣,所以编译器不知道如何抉择,会产生二义性错误。