# 模板简述
面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同的是,OOP处理的是在程序运行之前都未知的情况,而在泛型编程中,是在编译时期就已经知道了类型。
可以将模板理解为公式,当我们在使用的时候,提供足够的信息,编译器就会替我们生成特定的函数或者类,而不用编程人员为每一种类型都去实现大量重复的代码,大大提高了我们的编码效率。
函数模板
我们可以定义一个通用的函数模板(function template),不必为每个类型都去定义一个函数。
比如我们现在需要实现一个功能,就是交换两元素的值,我们可以使用函数重载来实现,对于不同的类型,我们不得不实现不同的函数,如下:
void Swap(int& elem1, int& elem2)
{
int temp = elem1;
elem1 = elem2;
elem2 = temp;
}
void Swap(double& elem1, double& elem2)
{
double temp = elem1;
elem1 = elem2;
elem2 = temp;
}
void Swap(char& elem1, char& elem2)
{
char temp = elem1;
elem1 = elem2;
elem2 = temp;
}
这三个函数大部分地方都相同,唯一的差别就是参数的类型不同。
现在有了函数模板,我们只需要写一个 “公式” 即可,如下:
template <typename Type>
void Swap(Type & elem1, Type & elem2 )
{
Type temp = elem1;
elem1 = elem2;
elem2 = temp;
}
模板定义以关键字 template 开始, 后面跟着一个用< > 包围起来的模板参数列表(template parameter list),其中写的便是用逗号分隔模板参数(template parameter)。
模板参数
模板参数列表和函数参数列表相似,我们在使用模板的时候,可以隐式或显式地指定模板实参(template argument),将其和模板参数绑定。
我们可以将typename 或class 关键字后面的类型参数看做类型说明符,就像使用内置类型一样使用,可以用来指定返回值类型或函数的参数类型,以及在函数体内用于声明变量和类型转换。
我们还可以在模板中定义非类型参数。
注意:在模板参数列表中,typename 和 class 并没有什么不同。
模板编译
当编译器遇到一个模板定义时,并不会生成代码,只有当实例化时,编译器去替我们生成代码,此时才会获知模板内代码的编译错误。通常编译器会在三个阶段报告错误。
- 在编译模板本身时:在这个阶段编译器可以检查语法错误。例如忘记分号或者将变量名拼写错误。
- 在编译器遇到模板使用时:通常会检查实参数目是否正确,检查类型参数类型是否匹配,在类模板,编译器可以检查用户是否提供了正确数目的模板实参。
- 在模板实例化时:依赖于编译器如何管理实例化,可以发现类型相关的错误。
模板函数特化
有时候并不是总能够写出对所有可能被实例化的类型都最适合的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译。
我们可以这样特化:
template<>
void Swap<string>(string& s1, string &s2)
{
string temp = s1;
s1 = s2;
s2 = temp;
}
类模板
类模板(class template)是编译器替我们用来生成类的公式。与函数模板不同的是,编译器不能为类模板推断模板参数类型,使用的时候,必须在模板名后面的尖括号中提供额外信息。
下面是一个类模板的使用实例:
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
A(const T& in_data)
:m_data(in_data)
{}
void Print()
{
cout << m_data << endl;
}
private:
T m_data;
};
int main()
{
A<int> a(20); // 必须指定模板参数类型
a.Print();
return 0;
}
模板的优点:有了复用代码的可能,节省资源,提高开发效率,编写与类型无关的代码,增加的灵活性。
模板的缺点:只有在实例化的时候,编译器才会编译模板部分的代码,所以必须要十分小心。出现模板编译错误的时候,错误信息比较混乱,不容易定位错误。
模板还有很多高级晦涩的用法,需要我们去不断学习。