问题引出
之前提到C存在命名冲突问题,新的C++专门为此引入了namespace机制加以改进(后文介绍),此外还有另一种机制,先看下面例子:
int add(int i, int j) { return i+i; }
float add(float a, float b, floatc) { return a+b+c; }
void main()
{
int a = add(8, 9);
float b = add(7.7, 8.8, 9.9);
}
代码在C环境下不成立,因为C编译器不允许定义两个同名函数,即使其参数个数和类型不同(参见C命名空间污染一节)。C下唯一的笨方法就是给这系列函数取很多不同名字,如add_int2、add_float3等(opengGL的接口就是这样)。
C++针对以上应用场景提出一种解决思路:除函数名外,把函数参数信息也加入进来进一步区分不同函数。即C++允许定义一组同名不同参的函数,编译器能够根据参数类型正确地区分并链接,如上例square(8)会调square(int),square(8.8)则去调square(float)。这种机制称为函数重载,它借助函数固有的参数信息,可以只使用单个函数名实现多个功能相似的函数,减少了对命名空间的占用。
注:习惯C函数调用与函数名一一对应后,起初对C++这种同样函数名包含多个功能(后续继承/多态也类似)可能会感觉别扭。另外overload翻译成重载容易让人想到覆盖,继而和继承/多态混淆。我更倾向叫它函数堆叠,就像叠罗汉J,更贴近点吧。
函数重载机制
理解函数重载,先要知道两个名词:修饰名(Decorated Name)与命名调整(Name Mangling)
C/C++程序中的函数在链接阶段对应的符号标识称为修饰名,是由编译器按某种规则生成的一个字符串,在compiling阶段创建,linking阶段使用。它就象函数的身份证号,必须唯一,后续才能被正确索引。
C函数的修饰名一般就是函数名(有些特殊形式如_udiv或__udiv多是来自C标准库),里面不包含参数信息,链接阶段自然无法区分同名不同参的函数。
C++针对性的改进思路就是:除函数名外,把函数固有的其它信息也加入修饰名(通常想到参数和返回值)。修饰名变的更长,如func_arg1type_arg2type…retvaluetype,从而为编译器提供更多特征去区分函数。
但最终C++标准中修饰名不包括返回值类型,即返回值不同而名字和参数相同的函数不是重载函数,为什么浪费这部分信息呢?
看两个函数void test(void);和int test (void);第一个没有返回值,第二个返回int。对于int x = test();可判断应调用第二个test()。可C++和C允许忽略返回值调用函数,如:test();这时返回值信息用不上,编译器甚至程序员自己都不知道该调哪个。(凡事有果必有因,现在奉为经典背诵学习的各种复杂规范,很多是基于旧标准的妥协和延伸罢了)。
C++相对C还多了类作用域,即不同类中同名同参的成员函数编译后的符号名也不能相同而导致混淆,所以类名也应加到修饰名里。还有新的C++还有命名空间的概念,位于不同namespace中的同名函数也要相互区分,因此namespace也应加入(所以命名空间机制也是基于往修饰名里插入人为设置的namespace标识而实现)。
下面给出一个简单例子以加深对