一、模板与泛型编程
我们前面实现简单的计算器的时候,我们会发现我们那会的计算器只能对整型数据进行简单计算,要是我们要对其他的数据也能进行简单计算,那么就需要用到我们的函数重载了,那么我们实现一个计算就要重复写这个函数了,而且这个函数中其实就只有函数的参数类型是不一样的,那么有没有什么好的解决办法吗?
下面我们看看我们没有学习模板前我们要实现一个通用的交换函数是咋样的:
虽然这样也可以,但是其有下面几个问题:
1、重载函数的仅仅是类型不同,其实现的逻辑都是一样的,这样导致代码的复用率低,当有新的 类型出现,那么又需要对这个类型再写一个函数。
2、代码的可维护性比较低,一个函数出错那么就可能导致其他的出错。
那么在C++中给我们提供了模板,这样让编译器根据不同的类型然后使用这个模板生成对应的代码。
而所谓的泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段,那么模板是泛型编程的基础。
而我们的模板分为函数模板和类模板
二、函数模板
1、概念
函数模板代表一个函数家族,该函数模板与类型无关,在使用的时候被参数化,根据实参类型产生函数的特定类型版本。
2、函数模板的使用
其中typename是用来定义模板参数关键字,也可以写class。
所以我们上面的写一个通用的交换函数,可以这样写:
上面的T只是一个名字,我们可以根据自己需求进行修改,比如x,y这样。
3、函数模板的原理
我们上面写好了交换函数Swap模板后,我们可以直接进行调用这个函数,而且对于不同的数据类型其都可以完成交换的操作。
可以看到使用了模板后其可以对不同类型的数据完成交换,那么其原理是咋样的呢?
实际上编译器收到这个几个函数后,其会根据我们已经写好的模板推演出所需要的类型,然后去生成对应的函数,这个过程叫做推演实例化。其本质上就是将原本要程序员干的活现在让编译器去帮我们完成了。
4、函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。函数模板实例化分为:隐式实例化和显示实例化。
(1)、隐式实例化
在上面的代码中,我们在调用函数的时候没有直接给函数知道数据的类型,而是让编译器自己去推演所需要的类型生成对应的函数,这种方法就是隐式实例化。
不过这样的话也会导致一些问题出现,就是我们使用这个函数的时候,要完成的是整型数据和浮点型数据的相加,但是我们的模板中我们就只有一个T,那么此时编译器就无法确定这个T是int还是double。
那么对于这种情况我们可以如下解决:
强制类型转换:
我们将整型的数据强制转换为浮点型:
还有一种方式就是显示实例化。
显示实例化:
其就是在函数名后面加个<>,然后指定模板参数的实际类型。
如下:
所以在无法直接推出模板参数T的类型的时候,那么我们就可以使用显示实例化。
5、模板参数的匹配原则
一个函数的模板可以与他同名的非模板的函数同时存在,相当于一个模板可以与他实例化出来的函数同时存在,并且这个模板函数还是可以被实例化为这个非模板函数。
但是这里又会有点冲突,就是我们对于已经实例化好的函数和模板函数,那么我们调用的时候实际上调用的是那个函数呢?
总结:
1、要是我们有现成的函数那么其就直接使用现成的,然后对于<int>这种显示实例化的方式就相当 于我们指定其去使用模板,所以其就调用模板实例化出来的函数。
2、对于非模板函数和同名模板函数,如果其他条件都相同,在调用时会优先调用非模板函数而不会从模板产生一个实例。但是如果模板可以产生一个具有更好匹配的函数,那么其将选择模板。
三、类模板
1、类模板的使用
下面我们举个例子看看:
我们前面学习数据结构的时候,我们还是使用的C语言实现的,那会我们就考虑到其存储的数据会有不同,所以那会我们使用typedef来指定一个数据类型。但是模板的话,我们对于类型就可以直接使用了,不用再去进行修改了。使用模板的话,就只需要一个模板就可以实现不同的数据类型了。
注:
在同一个文件中,类模板中函数的声明和定义分离时我们需要在函数定义处重新声明模板和指定类域。
所以不建议把类模板中函数的声明和定义分离到两个文件,因为这样会报链接错误,后续我们将模板进阶会细说。