两类多态:
第一类:编译决定。由编译器在程序的编译环节进行函数调用或资源访问的匹配决定,形成绑定串(bingding String)。
为了让程序能够适应变化,我们可以预先提供不同参数形式的同名函数。
1、函数重载(function overloading)
定义:同一名字空间域中,同名但不同参数形式的函数之间,互称函数重载关系
//main.cpp #include "f.h" int main() { //overload function 函数重载 // (chong zhong) 函数过载 //尽量不直接使用常量 int a = 5; float b = 5.6; f(a); //f@int f(b); //f@float return 0; }
| //f.h void f(int); int f(float);
| //f.cpp void f(int x) { //... }
int f(float y) {//... return 0; }
|
编译main.cpp时,生成绑定串,这就是编译决定:编译器通过检查调用和声明的关系,来生成绑定串;链接时,再根据绑定串去顺序链入定义体obj。
同名函数可以具有不同的形式(参数形式不同),这种多态由编译器根据参数类型来进行区别绑定。
重载是程序员主动编写的同名不同参的函数形式。
2、函数模板与模板函数
模板是程序员编写了通用模板形式,由编译器在编译期帮助展开的多态形式。
//compare.h //函数模板的隐式声明以及定义 template <typename XYZ> int compare(XYZ x,XYZ y) { return x<=y?0:1; } /* int compare(int x,int y) //int模板函数的隐式声明以及定义 { return x<=y?0:1; }
int compare(double x,double y) //double模板函数的隐式声明以及定义 { return x<=y?0:1; } */ | //main.cpp #include "compare.h"
#include <iostream.h> void main()//测试体 { int a = 1, b = 5; int ret = compare(a,b); //绑定为compare@int@int double c = 5.6, d = 5; //c< = d ret = compare(c,d); //绑定为compare@double@double cout<<ret<<endl;
} |
.h中放入的是函数模板的隐式声明,因此编译时满足函数模板的声明出现在调用之前;
同时又满足:链接时按照编译的绑定串能找到模板函数的定义。
如果我们不采用函数模板的隐式声明形式,还是使用三文件结构形式,就会造成编译成功,链接失败。
如下:
//compare.h //函数模板的显式声明 template <typename XYZ> int compare(XYZ,XYZ);
/* int compare(int,int y); // 编译main.cpp时替换生成的int版本 //模板函数的声明 */ | //main.cpp #include "compare.h"
#include <iostream.h> void main()//测试体 { int a = 1, b = 5; int ret = compare(a,b); //绑定为compare@int@int
double c = 5.6, d = 5; //c< = d ret = compare(c,d); //绑定为compare@double@double cout<<ret<<endl;
/*链接时报错:找不到上述两个绑定串指示的函数定义体obj*/
} | //compare.cpp //函数模板的定义 template <typename XYZ> int compare(XYZ x,XYZ y) { return x<=y?0:1; } /* 注意到这个文件编译,没有生成int模板函数的定义。 所以编译这个cpp虽然也成功,但在链接时报错。 */ |
根据三要素原则,链接各自编译成功的main.obj和compare.obj时报错。究其根源在于:编译发生时必须完成模板字符的替换,将函数模板替换生成为具体类型的模板函数。
小结:使用函数模板时,必须在.h内采用隐式声明的写法,方便调用与声明检查绑定时一次替换生成。通用模板字符版本的称为函数模板,编译阶段替换生成的称为模板函数。
3、类模板与模板类
除了函数模板这种形式,还有类模板的形式。也就是,这种模板类型,出现在函数的形参类型和数据成员的类型中。
//main.cpp #include "compare.h" #include <iostream.h> int main()//测试体 { int a = 1, b = 5; int ret = A<int>::compare(a,b); //编译绑定为A<int>::compare@int@int double c = 5.6, d = 5; //c< = d ret = A<double>::compare(c,d); //编译绑定为A<double>::compare@double@double cout<<ret<<endl; return 0; }
| //compare.h template <typename XYZ> //类模板的声明 class A { // XYZ i; public: static int compare(XYZ,XYZ); // static int compare(int,int); // 编译替换生成的模板类的函数声明
/*
//不建议使用下面的内联版本的隐式声明, //因为inline内敛函数的形式受到限制 //例如:递归/循环都不可用 //static int compare(XYZ,XYZ) //{ // return x<=y?0:1; // } */ };
//类模板的函数定义 template <typename XYZ> int A<XYZ>::compare(XYZ x,XYZ y) { return x<=y?0:1; } /*编译替换生成下面的模板类的函数定义 int A<int>::compare(int x,int y) { return x<=y?0:1; } */ |
小结:
1、类模板与模板类的关系类似于函数模板和模板函数的关系的,都是1:N:前者用编写的通用类型版本,后者是编译替换生成的具体版本。
2、类似于函数模板的使用,在类声明中既应包含类的类型声明,更要包含类中的所有成员函数定义。鉴于内联函数的局限性,一般不使用隐式声明的合并写法。
本章小结:
1、重载、函数模板与类模板都属于第一类多态(静态多态),也就是在编译期由参数类型决定访问绑定的函数是谁;
2、重载函数的不同版本由程序员自行编写提供;
3、模板的不同版本由编译器自动展开生成,模板函数的优先级高于重载函数,编译器总是试图将模板字符全部替换以生成调用体需要的声明类型;
4、模板字符的替换发生于编译期,因此,需要将模板的定义体和声明合并在一个.h文件中被include包含。