函数重载
- 自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”
函数重载概念
- 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。(在c语言中,函数体里是不能定义函数的)
- 大前提是必须在同一个作用域里面,如果不在同一个作用域里面是不行的
- 函数的名字必须相同,参数列表不同
- 重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能。这就是重载函数。重载函数常用来实现功能类似而所处理的数据类型不同的问题。不能只有函数返回值类型不同。
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
//参数列表不一样
//称其为函数重载
//调用的时候,调用那种的,就用哪种的
//编译器在编译的时候要进行参数类型的推演,他要确定你所传的参数到底是什么类型的
int main()
{
Add(1, 2);
Add(1.2, 3.4); //编译是可以通过编译的,但是计算的值是不对的
//c语言是不允许重名的函数的
return 0;
}
- 注意:函数重载和返回值类型没有什么关系,也就是说,如果两个函数只是返回值类型不同,是不能构成重载的
- 参数列表不同可以体现在 参数个数不同,参数类型不同,参数的类型次序不同
void TestFunc()
{
}
void TestFunc(int a)
{
}
//上面两个函数形成了重载
int main()
{
return 0;
}
void TestFunc(double a)
{
}
void TestFunc(int a)
{
}
//上面两个函数形成了重载
int main()
{
return 0;
}
void TestFunc(double a,int b)
{
}
void TestFunc(int a,double b)
{
}
//上面两个函数形成了重载
int main()
{
return 0;
}
看看下面的代码
void TestFunc()
{}
void TestFunc(int a = 0)
{}
int main()
{
TestFunc(10); //是可以通过编译的,会调用下面的哪个参数
TestFunc(); //是不会通过编译的
//对重载函数的调用不明确,第一个可以调用,第二个也可以调用
return 0;
}
下面两个函数可以形成函数重载吗?
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
答案是不可以的,因为只有返回值类型不相同,函数重载和返回值类型是没有关系的
C语言为什么不支持函数重载
- 在C语言中,编译器在编译时会将我们的函数重命名,具体的方法就是在我们的函数名前加上“_“修饰符,通过这种方式就可以在我们的符号表种查找到了,但是假如有两个相同的函数,编译之后进行相同的重命名,在符号表中生成的函数名一样,那么就无法区分到底是哪个函数了,所以这也就是我们的C语言为什么不支持函数重载的原因了。
名字修饰
通过我们在写好一个.c/.cpp文件的时候,点击开始运行,那么它就能运行起来,运行一个程序就这一步就好了吗?其实当然不是,下面让我们来看看我们C/C++中我们的程序到底是如何运行起来的。大致可以分为4个阶段,如下:
- 预处理阶段(在预处理阶段,会进行宏替换,条件编译,头文件展开,去店掉注释)
- 编译阶段(在编译阶段,首先会进行语法语义检错,无误后要将我们写好的C文件编译成汇编文件)
- 汇编阶段(在汇编阶段就是要将我们的汇编文件转换成可执行的机器指令)
- 链接阶段(在链接阶段就是要把我们的所有的目标文件以及我们所依赖的库文件链接到一起生成可执行程序)
以上就是我们一个程序的执行过程,我们可以看到,在链接文件的时候,假如我们写了一个重载函数add,那么我们的重载函数在我们的C++底层中是如何处理的?很容易想到,在编译过程中,编译器会将我们的代码编译成汇编文件,这里其实就存在这一种重命名机制,我们把它就叫做名字修饰。
C++中为什么支持函数重载
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接,最后形成可执行程序。
Name Mangling是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称。
C语言的名字修饰规则非常简单,只是在函数名字前面添加了下划线。比如,对于以下代码,在最后链接时就会出错
int Add(int left, int right);
int main()
{
Add(1, 2);
return 0;
}
编译器报错:error LNK2019:无法解析的外部符号_Add,该符号在函数_main中被引用。(C语言对函数名字的修饰是,只在函数名字前面加了一个下划线)
上述Add函数只给了声明没有给定义,因此在链接时就会报错,提示:在main函数中引用的Add函数找不到函数体。从报错结果中可以看到,C语言只是简单的在函数名前添加下划线。因此当工程中存在相同函数名的函数时,就会产生冲突。
由于C++要支持函数重载,命名空间等,使得其修饰规则比较复杂,不同编译器在底层的实现方式可能都有差异。
在vs下,对上述代码进行编译链接,最后编译器报错:error LNK2019: 无法解析的外部符号 “double cdecl Add(double,double)” (?Add@@YANNN@Z)
error LNK2019: 无法解析的外部符号 “int __cdecl Add(int,int)” (?Add@@YAHHH@Z)
通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在最终的名字中,就可保证名字在底层的全局唯一性
?’表示名称开始,‘?’后边是函数名“@@YA”表示参数表开始,后边的3个字符分别表示返回值类型,两个参数类型。“@Z”表示名称结束。
函数重载的作用
- 重载函数通常用来在同一个作用域内 用同一个函数名 命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。
C++运算符重载的相关规定如下:
- 不能改变运算符的优先级;
- 不能改变运算符的结合型;
- 默认参数不能和重载的运算符一起使用;
- 不能改变运算符的操作数的个数;
- 不能创建新的运算符,只有已有运算符可以被重载;
- 运算符作用于C++内部提供的数据类型时,原来含义保持不变。