C++静态多态性
C++静态多态性包括函数重载与缺省参数,运算符重载,宏多态,类的早起绑定。注意C语言没有重载机制。
函数重载
要深入理解函数重载需要了解下列几个问题:
- 什么是函数重载
- 为什么需要函数重载
- 编译器如何解决命名冲突,为什么不考虑返回值的类型
- 重载函数的调用匹配
- 编译器如何解析重载函数的调用的
1.1什么是函数重载
函数重载是指在同一作用域内,可以由一组具有相同函数名,不同参数列表的函数,这组函数被成为重载函数,常用它来命名一些功能相似的函数,这样做减少了函数名的数量,避免名字空间的污染,对于程序的可读性具有很大的好处。
/*
实现了重载函数print,让其既可以打印出int类型数据,也可以打印出string类型数据
*/
#include <iostream>
#include <string>
using namespace std;
void print(int a) {
cout << a << endl;
}
void print(string a) {
cout << a << endl;
}
int main() {
print(1);
print("hello freshman");
}1.2为什么要使用重载函数
- 若没由函数重载机制,每一个函数都不能同名,这样就要求当实现一个类似功能的函数时需要起很多名字,例如print_int(),print_string()等,这种做法很不友好。
- 类的构造函数和类名相同,一个类可能有多个构造函数,若没有重载机制,要实例化不同的对象比较麻烦。
- 操作符重载本质上是函数重载,大大丰富了已有操作符的含义
1.3编译器如何解决命名冲突的?
我们可以堆可执行文件进行反编译来做实验了解编译器是如何解决名字冲突的。使用objdump -d a.out > log.txt来对a.out可执行文件进行反汇编。
从log.txt中可以看出来,
函数void print(int)编译之后为:(其函数签名为_z5printi):
函数void print(string)编译之后为(其函数签名为_z5printSs):
我们可以发现,在编译之后,重载函数的名字都不是原来的print,这样就不存在命名冲突问题。
注意以下转换规则对应于g++编译器
然而编译器是如何将一个重载函数的签名映射到一个新的标识:返回类型+函数名+参数
从上面void print(int)->_z5printi, void print(string)->_z5printSs;
注意重载函数不考虑返回值的类型,不管返回值类型是什么都是以_z5开头,然后是函数名,然后紧跟着参数类型,当有多个参数的时候,几个参数罗列在后面,例如void print(int i, int j)->_z5printii;
类型的转换规则:int->i, char->c, long->l, string->Ss等等
观察重载函数需要注意由一句重要的限定:在同一作用域内。
#include <iostream>
using namespace std;
class test{
public:
void print(int i) {
cout << cout << i << endl;
}
void print(double d) {
cout << d << endl;
}
};
int main() {
test t;
t.print(1);
t.print(2.1);
}此时编译器将函数名映射为:void print(int i) -> (_ZN4test5printEi):
注意上面的N4test表示的是函数的作用域,所以g++中编译器的映射机制为:作用域+返回类型+函数名+参数列表
1.4重载函数的调用匹配
现在解决了重载函数命名冲突的问题,在定义完冲在函数之后,用函数名调用冲在函数最合适,需要按照下面的规则来判断调用那个函数:
- 精确匹配:参数匹配不做任何转换或者微不足道的转换,如数组名到指针,函数名到指向函数的指针,T到const T.
- 提升匹配:即整数提升(如bool到int,char到int,short 到int),float到double
- 标准转化匹配:如int到double,double 到int,double到long double,Drived* 到 base*;
- 使用用户自定义匹配
- 使用省略号匹配:类似printf中省略号参数。