十九. 模板
● 模板的基本概念
模板(template) 函数模板:可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。 语法: template <<模板的类型形参表>> <函数声明> 类模板:代表一簇类, 用户可以为类定义一种模式, 使得类中的某些数据成员, 某些成员函数的参数, 返回值或局部变量能取任意类型(包括系统预定义的和用户自定义的) 语法: template <<模板的类型形参表>> <类声明> ※ 泛型编程/泛化编程(generic programming):模板是泛型编程的基础. 泛型编程是独立于流行的面向对象编程的一种新的开发方式; 泛型编程的主要思想是将算法从特定的数据类型中抽象出来, 使算法成为通用的框架(frame), 它可以作用于各种不同的数据类型. |
● 函数模板的格式
函数模板的格式是: template <<模板形参表>> //实际写代码时, 只用一个尖括号 返回类型 函数名(形参表) { 函数体 } 注意: 1. 模板形参表有下面几种形式: ① class <数据类型标识符> //尖括号不用写, 下同 ② typename <数据类型标识符> ③ 类型说明符 <数据类型标识符> ※ ①②情况的模板形参称为模板类型参数, 类型参数(typename/class后面的标识符所代表的东西, 不同于传统函数中的数值形式的参数)可以用来指定函数模板本身的形参类型, 返回值类型, 以及函数体中的局部变量 ③情况的形参称为模板非类型形参, 类型说明符是说明变量是什么类型的标识符, 如int等; 这一种模板的类型形参与普通的函数形参的形式相同, 这说明这个形参的类型是确定的, 不像类型参数还需要推导它到底是哪个类型 2. 函数返回值类型可以是普通类型(如void),也可以是模板形参表中指定的类型。 函数模板定义后,就可以用它生成各种具体的函数(称为模板函数)。 |
//求绝对值 #include <iostream> using namespace std; template <typename T> T abs (T x) //其实就是把原来的普通的数据类型换成了这里的"类型参数"T { return x<0? -x: x; } void main() { int n=-5; double d=-5.5; cout<<abs(n)<<endl; cout<<abs(d)<<endl; } |
在上述主函数调用abs()时, 编译器可以从实参的类型推导出函数模板的类型参数T到底是什么类型. 但类型参数的类型确定后, 编译器将以函数为样板, 生产一个模板函数, 这一过程称为函数模板的实例化, 该模板函数称为函数模板abs的一个实例. 上例中的两个模板函数是: int abs (int x) { return x<0? -x: x } double abs (double x) { return x<0? -x: x } 注意: ① 函数模板本身在编译时不会生产目标代码, 只有模板生产的实例(模板函数)会生成目标代码; ② 被多个源文件引用的函数模板, 应当连同函数体一同放在头文件中, 而不能像普通函数那样只将声明放在头文件中; ③ 函数指针也只能指向模板的实例, 而不能执行模板本身 函数模板实例化分为显式实例化与隐式实例化: (1) 显式实例化: ,具体类型名2,..., 常量表达式> (实参表) 说明: ① 根据< >中给出的具体类型,用类似于函数调用实参与形参结合的方式,将模板参数表中的参数化类型一一实例化成具体的类型, 函数中的参数化类型也 一一实例化。 ② 如果模板参数表中有形式参数,还需要用常量表达式去初始化。 例如: 使用add<double>(8, 9)将T add(T x, T y) 实例化成: double add(double, double) 使用sum<int, 100> 将T sum() 实例化成: int sum(), size获得初值100; (2) 隐式实例化: 隐式实例化的格式为函数调用式,实例化过程是在实参与形参结合时,用实参的类型实例化形参对应的参数化类型。 例如: 使用add( 'A','B') 将T add(T x, T y) 实例化成: char add(char, char) 注意: 使用隐式实例化无法初始化模板参数表中的普通类型的形参,如果模板参数表中使用普通类型参数,必须使用显式初始化。 //上面的例子是隐式实例化, 下面是显式实例化的例子: |
#include <iostream> using namespace std; template <class type,int len> //定义一个模板类型, 模版参数有类型形参type, 也有非类型形参len type Max(type array[len]) //定义函数模板, 形参是type类型的包含len个元素的数组 { 个元素的值 for(int i=1; i<len; i++) //循环次数, 即比较大小的次数为len-1, 遍历数组元素; 个元素的值)要和array数组的第1个元素的值进行比较 { ret = (ret > array[i])? ret : array[i]; //ret用来存储经经比较后的值 } return ret; //返回经比较后得到的最大值 } void main() { int iset[5] = {1,2,3,4,5}; //定义一个整型数组 int iret = Max<int,5>(iset); //调用函数模板Max, 并使其实例化 cout<<iret<<endl; double dset[3] = {10.5,11.2,9.8}; //定义一个实数数组 double dret = Max<double,3>(dset); //调用函数模板Max cout << dret << endl; } |
int iset[5] = {1,2,3,4,5}; int iret = Max<int,5>(iset); 跟2比, 然后2跟3比, 然后3跟4比, 然后4跟5比; ; i<len; i++), 那么将会进行5次比较--首先iret(值为1)个iset数组的第0个元素比较, 以此类推 |
● 类模板定义的语法为
类模板定义的语法为: template <<模板形参表>> //类模板形参表与函数模板的形式是一样的 class 类名 //不是模板名 { 类成员声明; }; 模板类的成员函数还可以在类外定义,其语法如下: template <<模板形参表>> <返回类型> <类名> <<模板参数名表>>∷<函数名> <(参数表)> { 函数体; } 类模板实例化的语法:
类名 <<类型实参表>> 类名 <<类型实参表>> 对象1, 对象2, …, 对象n (实参表); 注意: ① 类模板中的成员函数可以是函数模板,也可以是普通函数, ② 一个类模板的类型参数实例化后, 即这个类模板被其它代码引用时, 类模板会被实例化, 生成具体的模板类. |
//例如,下面定义了一个模板类Student,为了增强类的适用性,将学号设计成参数化类型,它可以实例化成字符串、整型等; //将成绩设计成参数化类型,它可以实例化成整型、浮点型、字符型(用来表示等级分)等; template <class TNO, class TScore, int num> // TNO,TScore 为参数化类型 class Student { private: TNO StudentID[num]; //参数化(parameterized)类型(可变类型)数组,存储姓名 TScore score[num]; //参数化类型数组,存储分数 public: TNO TopStudent() //普通函数 { return StudentID[0]; } int BelowNum(TScore ascore) //成员函数模板 { return 0; } void sort() //普通函数 { } }; ////////////////////////////////////////////////// //模板类的成员函数还可以在类外定义 template <class TNO, class TScore, int num> class Student { private: TNO StudentID[num]; TScore score[num]; public: TNO TopStudent(); int BelowNum(TScore ascore); void sort(); }; template <class TNO, class TScore, int num> int Student<TNO, TScore, num>::BelowNum(TScore ascore) { return 0; } template <class TNO, class TScore, int num> void Student<TNO, TScore, num>::sort() { } template <class TNO, class TScore, int num> TNO Student<TNO, TScore, num>::TopStudent() { return StudentID[0]; } |
//类模板的具体案例 #include <iostream> using namespace std; template<class T1,class T2> //定义类模板, T1和T2代表类成员的数据类型, 不是类的类型 class MyTemplate { T1 t1; T2 t2; public: MyTemplate(T1 tt1,T2 tt2) //构造函数 { t1 =tt1; t2=tt2; //构造函数的功能是对数据成员赋值 } void display() { cout << t1 << ' ' << t2 << endl;} }; void main() { int a=123; double b=3.1415; MyTemplate<int ,double> mt(a,b); mt.display(); } |
//默认模板参数: 在类模板定义时, 为||类型形式参数中的一个或若干类型形参||赋予||默认值, 该默认值是一个普通的数据类型. 这样, 在生成新类时, 如果被赋予默认值的类型形参没有赋实参, 那么这个类型形参的值就是默认值 #include <iostream> using namespace std; template <class T1,class T2 = int> //int是模板形参的默认值 class MyTemplate { T1 t1; T2 t2; public: MyTemplate(T1 tt1,T2 tt2) //构造函数 {t1=tt1;t2=tt2;} void display() { cout<< t1 << ' ' << t2 << endl; } }; void main() { int a=123; double b=3.1415; MyTemplate<int ,double> mt1(a,b); MyTemplate<int> mt2(a,b); //第二个模板形参的没有被赋予实参, 因此该模板形参的值就是默认的int mt1.display(); mt2.display(); } |
//模板形参表中, 可以有一个非模板类型形参, 并且该形参已经被赋值: #include <iostream> using namespace std; template<class T1,class T2,int num= 10 > class MyTemplate { T1 t1; T2 t2; public: MyTemplate(T1 tt1,T2 tt2) //构造函数 {t1 =tt1+num; t2=tt2+num;} void display() { cout << t1 << ' ' << t2 <<endl;} }; void main() { int a=123; double b=3.1415; MyTemplate<int ,double> mt1(a,b); MyTemplate<int ,double ,100> mt2(a,b); //类模板生成模板类以后, 用模板类创建对象, 并赋值 mt1.display(); mt2.display(); } |
● 模板的特殊化/模板的定制(template specialization)
使用场景: 定义完函数模板或类模板以后, 我们发现有的数据类型不适用于已经定义好的模板(如下面例子中的char* 类型不适用于Type函数模板); 或者我们需要运用一个新的自定义的类类型(如下面例子中类模板的T应该对应的是一个普通的数据类型, 如int; 但如果我们要T对应一个自定义的Date类类型, 就需要特殊化/定制模板) 定义的目的: 补充模板, 从而扩展模板的功能 |
//函数模板的定制 #include <iostream > #include <string > using namespace std; template<class Type> Type min(Type a,Type b)//定义函数模板 { if(a < b) return a; else return b; } template <> //定义一个针对字符串的补充模板, 即函数模板的定制 char * min(char * a,char * b) // 上面的空模板形参表值允许在补充模板中使用, 可以把这个补充模板删去; 由于普通函数优先于模板函数, 因此能达到同样的效果, 但还是提倡用空的补充模板, 便于管理, 同时在没调用的情况下不会生产无用的目标代码. { if(strcmp(a,b)>=0) return b; else return a; } void main () { cout << "最小值:" << min(10,1) << endl; cout << "最小值:" << min('a','b') << endl; cout << "最小值:" << min("ah","ai") << endl; } //如果传送给上面这个模板的是指向字符串的字符指针, 即要求返回两个字符串中的较大者时, 模板是一句字符串的第一个字符来确定其大小的, 如果第一个字符相同, 则输出第一个字符串, 因而不能得出正确结果(就算正确结果本身就是第一个字符串, 那也不算真正的正确). |
//类模板的定制 #include <iostream> using namespace std; class Date { int iMonth,iDay,iYear; char Format[128]; public: Date(int m=0,int d=0,int y=0) { iMonth=m; iDay=d; iYear=y; } friend ostream& operator<<(ostream& os,const Date t) { cout << "Month: " << t.iMonth << ' ' ; cout << "Day: " << t.iDay<< ' '; cout << "Year: " << t.iYear<< ' ' ; return os; } void Display() { cout << "Month: " << iMonth; cout << "Day: " << iDay; cout << "Year: " << iYear; cout << endl; } }; template <class T> class Set { T t; public: Set(T st) : t(st) {} void Display() { cout << t << endl; } }; class Set<Date> { Date t; public: Set(Date st): t(st){} void Display() { cout << "Date :" << t << endl; } }; void main() { Set<int> intset(123); Set<Date> dt =Date(1,2,3); intset.Display(); dt.Display(); } |
● 类模板的派生与继承
(略) |