[toc]
前言
模板(template)是C实现代码重用机制的重要工具,是泛型技术(即与数据类型无关的通用程序设计技术)的基础。
模板是C中相对较新的语言机制,它实现了与具体数据类型无关的通用算法程序设计,能够提高软件开发的效率,是程序代码复用的强有力工具。
本章主要介绍了函数模板和类模板两类,以及STL库中的几个常用模板数据类型。
0x0100 概念&分类
0x0101 模板概念
模板是对具有相同特性的函数或类的再抽象,模板是一种参数多态性的工具,可以为逻辑功能相同而类型不同的程序提供一种代码共享的机制。
一个模板并非一个实实在在的函数或类,仅仅是一个函数或类的描述,是参数化的函数和类。
约定: 下文提到的所有的函数模板或模板函数指的都是用template关键字进行声明的函数. 未使用template关键字进行声明的函数称之为普通函数
0x0102 模板分类
- 函数模板
- 类模板
0x0103 函数模板
函数模板提供了一种通用的函数行为,该函数行为可以用多种不同的数据类型进行调用,编译器会据调用类型自动将它实例化为具体数据类型的函数代码,也就是说函数模板代表了一个函数族。
与普通函数相比,函数模板中某些函数元素的数据类型是未确定的,这些元素的类型将在使用时被参数化;与重载函数相比,函数模板不需要程序员重复编写函数代码,它可以自动生成许多功能相同但参数和返回值类型不同的函数。
0x0200 函数模板
0x0201 函数模板的定义
template <class T1, class T2,…>返回类型 函数名(参数表){
…… //函数模板定义体
}
template 是定义模板的关键字;写在一对<>中的T1,T2,…是模板参数,其中的class表示其后的参数可以是任意类型。
模板参数常称为类型参数或类属参数,在模板实例化(即调用模板函数时)时需要传递的实参是一种数据类型,如int或double之类。
函数模板的参数表中常常出现模板参数,如T1,T2
0x0202 使用函数模板的注意事项
- 在定义模板时,不允许template语句与函数模板定义之间有任何其他语句。
template <class T>
int x; //错误,不允许在此位置有任何语句
T min(T a,T b){…}
- 函数模板可以有多个类型参数,但每个类型参数都必须用关键字class或typename限定。 此外,模板参数中还可以出现确定类型参数,称为非类型参数。 例:
template <class T1,class T2,class T3,int T4>
T1 fx(T1 a, T 2 b, T3 c){…}
在传递实参时,非类型参数T4只能使用常量
-
不要把这里的class与类的声明关键字class混淆在一起,虽然它们由相同的字母组成,但含义是不同的。这里的class表示T是一个类型参数,可以是任何数据类型,如int、float、char等,或者用户定义的struct、enum或class等自定义数据类型。
-
为了区别类与模板参数中的类型关键字class,标准C++提出了用 typename 作为模板参数的类型关键字,同时也支持使用 class 。比如,把min定义的template
写成下面的形式是完全等价的:
template <typename T>
T min(T a,T b){…}
0x0300 函数模板的实例化
0x0301 实例化发生的时机
模板实例化发生在调用模板函数时。当编译器遇到程序中对函数模板的调用时,它才会根据调用语句中实参的具体类型,确定模板参数的数据类型,并用此类型替换函数模板中的模板参数,生成能够处理该类型的函数代码,即模板函数。
- 当多次发生类型相同的参数调用时,只在第1次进行实例化。假设有下面的函数调用:
int x=min(2,3);
int y=min(3,9);
int z=min(8.5);
编译器只在第1次调用时生成模板函数,当之后遇到相同类型的参数调用时,不再生成其他模板函数,它将调用第1次实例化生成的模板函数。
0x0302 实例化的方式
- 隐式实例化
编译器能够判断模板参数类型时,自动实例化函数模板为模板函数
template <typename T> T max (T, T);
…
int i = max (1, 2);
float f = max (1.0, 2.0);
char ch = max (‘a’, ‘A’);
…
隐式实例化,表面上是在调用模板,实际上是调用其实例
- 显示实例化explicit instantiation
若编译器不能判断模板参数类型或常量值, 需要使用特定数据类型实例化
语法形式:
模板名称<数据类型,…,常量值,…> (参数)
template <class T> T max (T, T);
…
int i = max (1, ‘2’);// error: data type can’t be deduced
int i = max<int> (1, ‘2’);
…
0x0400 函数模板的特化
0x0401 特化的原因
但在某些情况下,模板描述的通用算法不适合特定的场合(数据类型等)
比如:如max函数
char * cp = max ("abcd", "1234");
实例化为:char * max (char * a, char * b){return a > b ? a : b;}
这肯定是有问题的,因为字符串的比较为:
char * max (char * a, char * b)
{ return strcmp(a, b)>0 ? a : b; }
0x0402 特化
所谓特化,就是针对模板不能处理的特殊数据类型,编写与模板同名的特殊函数专门处理这些数据类型。
模板机制为C++提供了泛型编程的方式,在减少代码冗余的同时仍然可以提供类型安全。 特化必须在同一命名空间下进行,可以特化类模板也可以特化函数模板,但类模板可以偏特化和全特化,而函数模板只能全特化。 模板实例化时会优先匹配”模板参数”最相符的那个特化版本。
模板特化的定义形式:
template <> 返回类型 函数名<特化的数据类型>(参数表) {
//TODO
}
// 普通模板函数
template <class T>
int max(const T lhs, const T rhs)
{
return lhs > rhs? lhs: rhs;
}
// 函数特化
template <>
int max<int>(const int lhs, const int rhs)
{
return lhs > rhs? lhs: rhs;
}
// 普通类模板
template <class T1, class T2>
class A
{
T1 data1;
T2 data2;
};
// 全特化类模板
template <>
class A<int, double>{
int data1;
double data2;
};
// 半特化类模板
template <class T2>
class A<int, T2>
{
int data1;
T2 data2;
};
说明:
- template <>是模板特化的关键字,<>中不需要任何内容;
- 函数名后的< >中是需要特化处理的数据类型。
- 当程序中同时存在模板和它的特化时,特化将被优先调用;
- 在同一个程序中,除了函数模板和它的特化外,还可以有同名的普通函数。其区别在于C++会对普通函数的调用实参进行隐式的类型转换,但不会对模板函数及特化函数的参数进行任何形式的类型转换。
0x0403 调用顺序
当同一程序中具有模板与普通函数时,其匹配顺序如下:
- 完全匹配的非模板函数
- 完全匹配的模板函数
- 类型相容的非模板函数
0x0500 类模板
0x0501 类模板的概念
类模板可用来设计结构和成员函数完全相同,但所处理的数据类型不同的通用类。
如:
双精度栈:
class doubleStack{
private:
double data[size];
……
};
字符栈:
class charStack{
private:
char data[size];
……
};
这些栈除了数据类型之外,操作完全相同,就可用类模板实现。
0x0502 类模板的声明
template<class T1,class T2,…>
class 类名{
……// 类成员的声明与定义
}
其中T1、T2是类型参数
类模板中可以有多个模板参数,包括类型参数和非类型参数
0x0503 类模板的声明
在下面的模板参数表中,T1、T2是类型参数,T3是非类型参数。
template<class T1,class T2,int T3>
在实例化时,必须为T1、T2提供一种数据类型,为T3指定一个整常数(如10),该模板才能被正确地实例化。
0x0504 类模板的成员函数的定义
在类模板外定义,语法
template <模板参数列表>
返回值类型 类模板名<模板参数名表>::成员函数名 (参数列表)
{
//TODO
};
template <class T>
void Stack<T>::push(T const& elem)
{
elems.push_back(elem);
}
0x0504 类模板特化
解决例9-9的方法是特化。即用与该模板相同的名字为某种数据类型专门重写一个模板类。
类模板有两种特化方式:
- 特化整个类模板
- 特化个别成员函数
特化成员函数的方法:
template <> 返回类型 类模板名<特化的数据类型>::特化成员函数名(参数表){
//函数定义体
}
template<>
void Array<char *>::Sort(){
for(int i=0;i<Size-1;i++){
int p=i;
for(int j=i+1;j<Size;j++)
if(strcmp(a[p],a[j])<0)
p=j;
char* t=a[p];
a[p]=a[i];
a[i]=t;
}
}