面向对象和泛型编程在C++的编程思想中占据绝对的地位,而模板又是泛型编程的主要内容。
函数模板
1. 普通模板
//以Swap名称为例 template <typename T0,typename T1> //or class
void Swap(T0 &a, T1 &b)
{......}
2. 具体化
隐式实例化、显式实例化、显式具体化统称为具体化。
(1) 显式具体化(可以写成两种形式)
//以Swap名称为例 template <> void Swap<int>(int &a, int &b) {......} //or /* template <> void Swap(int &a, int &b) //<int>是可选的 {......} */
(2)显式实例化
在模板声明中用符号‘<>’显式的指出类名。
//以Swap名称为例 template void Swap<int>(int , int );
(3)隐式实例化
依赖输入实参类型实例化模板。
//以Swap名称为例
//模板声明 template <Typename T0, typename T1> void Swap(T0 &, T1 &) //程序入口 void main() { int a = 1; int b = 2; Swap(a, b);//隐式实例化为函数定义void Swap(int &, int &){} ........ }
3. 使用哪个函数版本
对于函数重载、函数模板、函数模板重载,C++有一个良好的策略,决定函数调用使用哪一个函数定义。这个过程称作重载解析。
对此,编译器需要决定哪一个可行函数是最佳的,从最佳到最差的顺序如下:
一、完全匹配,常规函数优先于模板;
二、提升转换(如char,short —> int或float —> double)
三、标准转换(数值类型之间的非提升转换)
四、用户(用户指使用模板定义的程序员)定义的转换(?用户定义的转换是什么,怎么表示?如何直观地验证其优先级最低?)
(1)完全匹配和最佳匹配
完全匹配允许一些无关紧要的转换(如数组和指针、引用和变量之间)
有两个函数完全匹配是一种错误,但这一规则有两个例外:
一个是指向非const的引用或指针优先与非const指针或引用匹配,其次与const指针或引用匹配(注:只限于引用或指针);
另一个是当两个函数完全匹配时将选择最具体(需要进行的转换最少)的版本。
(2)创建自定义选择
(?)在函数调用中显式地使用符号‘<>’使模板函数具体化。(?)
类模板
1. 类模板语法规则
(1)类声明和定义
如果在类声明中定义了方法(内联),则可以省略模板前缀和类限定符;
不能将模板成员函数放在独立的实现文件中。
1 //a.h 2 #ifndef A_H_ 3 #define A_H_ 4 #include<iostream> 5 using namespace std; 6 7 template<class T> 8 class A 9 { 10 T t; 11 public: 12 A() : t(0){} 13 void show(){std::cout << t << std::endl;} //内联 14 void view();//模板类方法声明 15 }; 16 17 template<class T>//模板类方法定义 18 void A<T>::view() 19 { 20 std::cout << "view T: " << t << std::endl; 21 } 22 23 #endif
(2)模板类的使用
1 #include"a.h" 2 3 void show(int &); 4 5 void main() 6 { 7 A<int> a; 8 a.show(); 9 system("pause"); 10 }
2. 模板的参数
(1)使用非类型参数
模板中由typename或class限定的类型称作类型参数;已知类型称作非类型(或表达式)参数。
template<typename T, int n> // T为类型参数,n为非类型参数。
非类型参数可以是整型、枚举、引用或指针。(double m是不合法的,但double &和double*是合法的)
模板代码不能修改参数的值,也不能使用参数的地址。
实例化模板时,用作非类型参数的值必须是常量表达式(比如不能把变量赋给模板类array<int, n>中的非类型参数n,array类相对于vector类运行速度快,适合处理大量小数组)。
(2)具体化的模板类可以作为其它模板的类型参数
Array < Stack<int> > asi //外层尖括号内侧最好用空格隔开
(3)可以将具体化的模板用作自身的类型参数,达到递归的目的。
1 typedef array ArrayTP; 2 ArrayTP< ArrayTP<int, 5>, 10 > twodee; //等价于int twodee[10][5]
(4)将模板用作参数
模板可以包含本身就是模板的参数。
1 template< template<typename T> class Thing > 2 class Crab 3 { 4 ...... 5 };
(5)使用多个类型参数
模板可以使用多个类型参数。
(6)给模板参数提供默认值
可以给类模板参数提供默认值,但不能给函数模板参数提供默认值,可以为非类型参数提供默认值。
1 template<class T1, class T2 = int, int n = 0> 2 class ToPo 3 { 4 }
3. 类模板的具体化
(1)隐式实例化
ArrayTP<double, 100> stuff;
(2)显式实例化
template class ArrayTP<string, 30>;
(3)显式具体化
当对特殊类型实例化时,要对模板进行修改,使其行为不同。这种情况下可以创建显式具体化。
template <> class Classname<specialized-type-name>{......};
(4)部分具体化
//普通模板 template <class T1, class T2> class Pair{......}; //具体化T2为int template <class T1> class Pair<T1, int>{......};
4. 模板成员
我们提到过数据成员,函数成员,这里引入模板成员。
模板可以作为结构、类和模板类的成员。
5. 模板类和友元
模板类的声明也可以有友元。模板的友元分三类:
1. 非模板友元(类似于普通类的友元);
2. 约束模板友元;
使得模板类的每一个具体化,都获得与友元匹配的具体化。包含以下三部:
(1)在类定义的前面声明每个模板函数;
(2)在函数中再次将模板声明为友元;
(3)为友元函数提供模板定义。
3. 非约束模板友元。
对于非约束化模板友元,友元模板类型参数与类模板类型参数是不同的。
在类定义中声明模板函数,并声明其为友元;之后再为模板函数提供友元定义。