泛型编程:编写与类型无关的逻辑代码,是一种代码复用的手段,模板是泛型编程的基础。
模板分为函数模板和类模板。
【函数模板】
代表一个函数家族,该函数与类型无关,在使用时被参数化,根据参数的类型产生特定类型的函数。
模板函数的格式:
template<
typename T1,
typename T2,
typename T3, ......>
返回值类型 函数名(参数列表)
{
函数体
......
}
typename 是用来定义模板参数的关键字,可以用class代替。
模板函数可以定义为
inline函数
注意:
模板是一个蓝图,本身不是类或者函数,编译器会用它生成指定类型的类或者函数,产生特定类型函数的过程叫做函数模板的实例化。
注意:模板被编译了两次:
- 在实例化之前,检查代码本身有没有语法错误
- 在实例化期间,检查模板代码,查看是否所有的调用都是有效的(例如,实例化类型不支持某些函数调用)
实参推演
从函数实参确定模板参数形参类型和值的过程称为模板实参推演
多个类型形参的实参必须完全匹配
模板形参分为:类型形参和非类型形参
- 模板形参名字只能在模板形参之后到模板声明或者定义的末尾之间使用,遵循名字屏蔽规则
- 模板形参的名字在同一模板形参类表中只能使用一次
template<typename T, typename T>
void Fun(T t1, T t2)
{
}
这样是错误的,T会发生重定义
- 类型形参转换
编译器只会执行两种转换:
-
- const转换:接收const引用或者指针的函数可以分别用非const对象的引用或者指针来调用
- 数组或者函数到指针的转换:如果模板参数不是引用类型,则对数组或者函数类型的实参应用常规指针转换。数组实参当做指向第一个元素的指针,函数实参当做指向函数类型的指针。
- 非模板类型参数
非模板类型参数是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数
如下:x作为数组长度就使用的是非模板类型参数
类型等价性:
int array[5] = { 0 };
Array(array);
const int a = 20;
int array2[a - 15] = { 0 };
Array(array2);
发现他们其实是带用了同一个被实例化的函数,编译器没有额外合成新的函数。
模板形参说明:
- 定义模板函数时,模板形参表不能为空
- 模板形参可以是类型形参,也可以是非类型形参,类型形参跟在class和typename之后class和typename含义相同,可以互换。
- 模板类型形参可以作为说明符出现在模板的任何地方,与内置类型或者自定义类型的使用方法完全相同。可用于指定函数形参类型,返回值,局部变量和强制类型的转换。
- 在模板函数的内部不能指定缺省的模板类型实参
模板函数重载
template <typename T>
T Add(T data1, T data2)
{
return data1 + data2;
}
template <typename T>
T Add(T data1, T data2, T data3 )
{
return data1 + data2 + data3;
}
int Add(int data1, int data2)
{
return data1 + data2;
}
int main()
{
Add(1, 2);
Add(1, 2, 3);
Add(2, 'b');
Add<char>('a', 'b');
system("pause");
return 0;
}
注意:
- 一个非模板函数可以和一个同名的普通函数同时存在,而且该函数模板还可以实例化为这个非模板函数。
- 对非模板函数和同名模板函数,如果其他条件相同,在调用时会优先调用非模板函数,如果模板可以产生一个具有更好匹配的函数,那么将选择模板
- 显示指定一个空的模板实参列表,这个编译器就知道只有模板函数才能匹配这个调用而且所有的模板函数都应改根据实参演绎出来
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
特化:
在某些情况下,通用模板定义对于某个类型来说是完全错误的,或者编译都不能通过,这个时候就需要进行模板函数特化来解决这样的问题
对于上面的Add()函数来说,它并不能完成对字符串的处理,所以我们要进行特化
代码如下:
template <typename T>
T Add(T data1, T data2)
{
return data1 + data2;
}
//特化
template<>
char* Add<char*>(char* const data1, char* const data2)
{
char* tmp = new char[strlen(data1) + strlen(data2) + 1];
strcpy(tmp, data1);
strcat(tmp, data2);
return tmp;
}//特化结束//
template <typename T>
T Add(T data1, T data2, T data3 )
{
return data1 + data2 + data3;
}
int Add(int data1, int data2)
{
return data1 + data2;
}
int main()
{
char *a = "hello";
char *b = " world";
Add(1, 2);
Add(1, 2, 3);
Add(2, 'b');
a = Add(a, b);
cout << a << endl;
system("pause");
return 0;
}
函数模板特化形式:
template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}
注意:
- 特化的声明与特定的模板相匹配
如下,特化一个返回值为char*具有两个参数为char*类型的Add()函数
- 特化时必须有模板形参表,否则就只是定义一个普通函数
- 特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头文件
- 在模板特化版本的调用中,实参类型必须与特化版本的形参类型保持一致,如果不匹配,则不会使用该特化函数,只会在实参模板中实例化一个实例
【类模板】
模板格式:
template <typename 形参名>
class 类名
{
......
};
注意:
- 在使用类模板时,需要我们显示的给出模板实参列表,否则编译器无法得知实际的类型。
- 类模板的成员函数可以在类模板的定义中定义(inline函数),也可以在类模板定义之外定义(此时成员函数定义前面必须加上template及模板参数)。
- 类模板成员函数本身也是一个模板,类模板被实例化时它并不自动被实例化,只有当它被调用或取地址,才被实例化。