一、函数重载的概念
通俗的讲:重载就是一个词有多种不同的含义。准确地说:在C++程序中,将在同一作用域内,语义、功能相似具有相同函数名字,不同参数列表(参数的个数,顺序和类型不同)的函数称为函数重载。
二、函数重载的作用
1、函数重载便于记忆,提高了函数的易用性。
2、类的构造函数需要重载机制。因为C++规定构造函数必须与类名相同,如果没有函数重载,想实例化不同的对象是很麻烦的。
3、操作符重载的本质就是函数重载。因为C++中运算符的语义都是固定的,而且就那么几个运算符,如果想让同一个运算符同时能支持对不同类型对象的操作就必须使用重载机制。
三、函数重载的实现——名字修饰
因为在最终的二进制可执行程序是不允许有同名函数出现的,所以作为语言实现的编译器必须为每一个被重载的函数生成唯一的内部变量。如何实现?采用名字修饰机制。
1、什么是名字修饰?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。Name Manging是一种在编译过程中,将函数、变量名称重新改编的机制。简单来说就是编译器为了区分各个函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。
2、C语言的名字修饰
C语言的名字修饰非常简单,只是在函数名字前面添加了下划线。比如,对于以下代码,在最后链接时就会出错:
【例1】在VS2013 C语言编译器编译
int add(int i, int j);
int main()
{
add(8, 8);
return 0;
}
编译结果:
释:上述add函数只给了函数声明,但是没有定义,因此在链接时就会报错。在提示中,main函数引用的add函数找不到函数体,从报错结果中可以看到,函数在C语言编译器以在函数名前加下划线的形式作为编译后的名字。因此,当工程中存在相同函数名的函数,C语言编译器就会报错。
3、C++的名字修饰
C++编译器实际在底层使用的不是add名字,而是被重新修饰过的一个比较复杂的命名,被重新修饰后的名字中包含了:函数的名字以及参数类型。
【例2】在VS2013 C++编译器编译,与例1的代码相同
int add(int i, int j);
int main()
{
add(8, 8);
return 0;
}
编译结果:
释:
(1)编译后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过函数名字进行重新修饰,将参数类型包含在最终的名字中,就可以保证名字在底层的全局唯一性。
(2)其实,该函数编译后能被编译器识别的名称是“?add@@YAHHH@Z”。
三、函数重载需要注意的问题:
1、不能仅靠返回值类型的不同来区分重载函数。因为如果同名函数仅仅是返回值类型的不同,有时可以区分,有时却不能。
【例3】
void Function(void);
int Function(void);
释:上述两个函数,第一个没有返回值,第二个返回值类型为int。当使用如下语句:“int x = Function();”将存在歧义,因为不确定调用的是第一个函数还是第二个函数。
2、并不是两个函数名相同就能构成重载。如:全局函数和类的成员函数即使同名,也不算重载,因为它们的作用域不同。
【例4】
void Print(...);//全局函数——文件作用域
class A
{
...
void Print(...);//成员函数——类作用域
}
释:不论两个Print函数的参数是否相同,都不能构成函数重载,因为两个函数的作用域不同。如果想调用全局的Print函数,都需要在函数前加一元作用域解析运算符“::”,否则成员函数将遮蔽同名的全局函数。
3、小心隐式类型转换导致重载函数产生二义性
【例5】
# include<iostream>
using namespace std;
void output(int x)
{
cout << "output int " << x << endl;
}
void output(float x)
{
cout << "output float " << x << endl;
}
int main()
{
int x = 1;
float y = 1.0;
output(x); //output int 1
output(y); //output float 1.0
output(1); //output int 1
//output(0.5); //error!不明确的调用,因为自动类型转换
output(int(0.5)); //output int 0
output(float(0.5)); //output float 0.5
system("pause");
return 0;
}
运行结果为:
释:第一个output函数的参数为int类型,第二个output函数的参数是float类型。语句output(0.5)将产生编译错误,因为0.5的类型为double,而一个double临时变量既可以转换为int型,也可以转换为float型,二者都是标准转换。
注:隐式类型转换在很多地方可以简化程序的书写,但是也留下隐患。
四、面试题:
在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明?
答:C++支持重载,而C语言不支持重载。函数被C++编译器编译和被C编译器编译后生成的内部名字是不同的,前者能显示到参数的类型,而后者不能。C++提供了C连接交换指定符号extern“C”来解决这个问题,即二进制兼容性问题。