模板指C++能够将数据类型作为一个可变化的数据类型形参进行定义的功能。
模板就是将类型参数化
函数模板 定义函数模板的写法如下:
template返回值类型 模板名(形参表) { 函数体 }
其中的 class 关键字也可以用 typename 关键字替换,例如:
template
举例:
在调用函数模板时,编译器根据实参的类型自动确定模板实际参数类型,对函数模板进行实例化,即生成处理确定类型的函数,并调用它 。
#include常见的模板设计错误using namespace std; template void Swap(T & x, T & y) { T tmp = x; x = y; y = tmp; } int main() { int n = 1, m = 2; Swap(n, m); //编译器自动生成 void Swap (int &, int &)函数 double f = 1.2, g = 2.3; Swap(f, g); //编译器自动生成 void Swap (double &, double &)函数 return 0; }
- 没有把关键字class或typename放在函数模板的每一个形式参数之前。
template< T1, T2> //error
template
-
函数模板中的某些形式参数,在后面的函数参数列表中没有被用到。
例:template
TYPE* func( ){…}
void main(){ char* s = func(); }
尽管func( )的返回值用到了模板形参TYPE,但函数的参数表中没有用到这个模板形参。在调用func()时,编译器无法根据实参确定究竟应该用什么具体的类型来代替模板参数TYPE。所有类型都应在参数表中
-
函数体中使用了模板形参,但函数参数表中没有用到它,编译时将产生一个错误信息。(无法推断T的类型)
例:
template
void func( ) {TYPE obj; … }
void main() { func(); }模板的形参一定要出现在函数参数表中。
引例:
templateT max1(T a, T b) { return (a > b) ? a : b; } void fun(int num, char cha) { max1(num, num); max1(cha, cha); max1(num, cha); //错误,无法匹配 max1(cha, num); //错误,无法匹配 } 这里出现错误。问题在于模板类型并不知道int和char之间能进行隐式类型转换。但是,这样的转换在C++语言中是很普遍的。
为了解决这个问题,可以在一个函数模板中使用多个模板参数或者重载一个函数模板。
templateT max1(T a, D b) { return (a > b) ? a : b; }
用户可以用非模板函数重载一个同名的函数模板。例如,可以这样定义
templateT max1(T a, T b) { return (a > b) ? a : b; } //显式的声明max1(int, int),重载的非模板函数 int max1(int a, int b) { return (a > b) ? a : b; } void fun(int num, char cha) { max1(num, num); //调用 max(int, int) max1(cha, cha); //调用 max(T, T) max1(num, cha); //调用 max(int, int) max1(cha, num); //调用 max(int, int) } 非模板函数 max1(int, int) 重载了函数模板 max1(T a,T b),当出现调用语句 max1(num, cha);和 max1(cha, num);时,它执行的是重载的非模板函数的版本 max1(int, int)。
调用的顺序是:首先寻找一个参数完全匹配的非模板函数,如果找到了就调用它;若没有找到,则寻找函数模板,将其实例化,产生一个匹配的模板参数,若找到了,就调用它。
当一个实际函数调用时,可以和一个重载函数匹配、经参数 转换后与某一个重载函数匹配、又可以和某一个模板函数相匹配,这都不会出现二义性。只是调用哪一个函数要遵照一定的规则按先后次序调用。这就是函数模板和同名非模板函数的重载都要遵守的约定。
类模板在设计一个类时,将数据类型作为类的参数。作为参数的类型可以是C++语言提供的基本数据类型和复合数据类型,也可以是自定义的类类型。
定义:templateclass 类名字 { …… }; 其中,用尖括号括起来的是模板形式参数表,它列出了类模板的每个模板形式参数,多个模板形式参数之间用逗号分隔开。每一个模板参数由保留字class引入。
注意:
-
用template引出模板形式参数;
-
类定义中数据类型使用模板形式参数。
-
这种形式的类不能直接用于创建对象实例。
-
在类定义之外实现成员函数时,必须逐一指明模板参数,否则产生语法错误。
在类模板体外定义的成员函数的一般形式如下:
template
函数类型 类名<类型参数>::成员函数名(形参表) { ····· } 例: template T Three ::sum() { return x + y + z; }
一般形式:
类模板名<实际类型表> 模板类对象名(构造函数参数表);
例如:
Listint_list; List float_list; ↑ ↑ 模板类,类型 具体对象
分析:List
实际上进行了两个实例化步骤:
-
首先根据List
将类模板List实例化为一种具体数据类型 List ,模板类 -
然后根据类类型List
创建一个对象实例 int_list。
类模板的继承关系
类模板的派生与普通类的派生一样,分为公有派生类、保护派生类和私有派生类。继承成员的访问控制规则也是相同的。
类模板和类模板之间、类模板和类之间可以互相继承。它们之间的派生关系有以下四种情况
1)、类模板继承类模板
2)、类模板继承模板类
3)、类模板继承普通类
4)、普通类继承模板类
注意:
(1)派生类的模板形式参数表中必须包含基类的所有模板形式参数。
(2)在派生类模板的实例化时,实际模板参数也传递给了基类模板,从而完成了基类模板的实例化工作
例:
#include本章重点using namespace std; //1、类模板继承类模板 template class A { T1 x; T2 y; }; template class B : public A { T1 x1; T2 y2; }; template class C : public B { T x3; }; //2、类模板继承模板类 template class D : public A //具体化的模板类 { T x4; }; //3、类模板继承普通类 class E { int x4; }; template class F : public E { T X5; }; //4、普通类继承模板类 template class G { T g; }; class H : public G { int h; }; int main() { //1、类模板继承类模板 C c; //由派生的具体类型反推 基类 模板类型 C B A B b; //由派生的具体类型反推 基类 模板类型 B , A //2、类模板继承模板类 D d; //生成D 和 A 模板类 //3、类模板继承普通类 F f; //生成 F //4、普通类继承模板类 H h; //生成 G 模板类 return 0; }
掌握模板的概念
掌握函数模板实现及非模板函数重载
掌握函数模板、非模板函数的调用顺序
掌握类模板实现
掌握类模板的类外实现方法
掌握类模板的友元函数声明方法
掌握类模板、函数模板的实例化
了解类模板的继承和派生



