一、模板的理解与使用
首先要理解一个基本事实,模板的作用是让我们程序员少写代码,以此来简化开发流程的。
它对于最终编译生成的代码并没有任何影响。
所以即使不用模板,大多数时候我们同样也能够实现相关的功能。
这里以一个简单通用的代码来理解模板:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
比如像上面这两个函数类似的一系列函数,它们函数名字完全相同,代码逻辑也完全相同,唯一不同的是它们所操作的数据类型,一个为int
,一个为double
,甚至还可以继续添加其它数据类型。
那么使用模板就可以将其简化为一段代码:
template<typename T>
T add(T a, T b) {
return a + b;
}
此时我们不再使用特定的int
、double
之类的类型,而是使用一个类似于变量的T
类型。
方法就是在函数前面写上一个模板关键字template
,然后在其后的<>
中声明模板变量名。
声明模板变量,使用typename
或者class
均可,只不过由于class
一般使用于类,所以模板声明变量的时候大多数时候应该还是使用的typename
,便于区分。
如果需要有多个模板变量,直接跟在后面写即可:template<typename T,typename T1>
当然这里的T
,你也可以自己随便取其它名字,只不过习惯取T
,即Type
的首字母。
此时这个函数要操作的类型就是不确定的了,所以我们在使用的时候就需要指定类型:
add<int>(1,3);
add<double>(1.1, 3.4);
add<char>('s','a');
也就是在add
的后面跟上一个<>
,里面填上我们想要使用哪种类型,然后就可以像正常函数那样使用了。
是不是有种似曾相识的感觉?STL模板库不就是这么用的嘛!
同时要注意,它并不是只有一个函数,虽然此时看上去我们只写了一个函数,但这只是表象。
实际上是编译器帮我们根据上面的那个模板函数,分别生成三个操作int
、double
、char
类型的函数。
进一步来说,模板是完全给编译器看的,并不会参与到最终的可执行文件中
上面这一点便是模板的精髓!
为了更加直观的理解,我们来看一下最终生成的三个函数的内存地址:
总结来说就是,模板并没有减少最终的代码量,它仅仅只是减少了我们程序员需要写的代码量。
并且这个过程是在编译期间就完成了的,这一点很重要!
二、模板类
除却用在函数中,模板还可以用在类上,比如我们使用的STL库,基本全是用模板实现的,所以看上去才那么怪异,一旦出错就很难排查,这也是使用模板的缺点之一。
模板类与模板函数使用方法都是一样的,在前面添加template
然后声明模板变量,之后就可以在这个模板类中随意使用这个模板变量了,将它视为一个真正的类型使用即可。
同时在调用的时候,你还需要指定它的类型,就和使用STL
中的stack
库是一样的。
三、注意事项
虽然模板使用起来确实简单,可以简化我们程序员的代码量。
但也有一些要注意的事项,比如,你的模板类中的函数,声明与实现都要写在头文件中才行,否则就会报错。
首先是声明与实现都写在头文件中:
然后是分开写,但同样都在头文件中:
注意模板分开写时,每个函数前面都必须要写上模板变量,比较麻烦,但依旧正确。
一旦你将两者分开成两个文件,就会开始报错。
其原因在于这个类事先并不存在,只有当我们使用了Stack<int>
时,编译器才会开始实例化一个操作int
类型的类,一旦分开写,编译器就找不到对应的实现文件,从而报错。
可能的原因就是,是cpp
文件包含的h
文件,而main
所在文件包含的也是h
文件,编译器也就只会去实例化h
文件中的代码。
如果将main
文件中的#include"Stack.h"
替换为#include"Stack.cpp"
就不会报错了,但这样做就有点多此一举了,不如直接将代码全写在h
文件中。