第十章 模板 丘志杰 电子科技大学 计算机学院 软件学院
C++最重要的特征之一就是代码重用,为了实现代码重用,代码必须具有通用性。通用的代码需要不受数据类型的影响,并且可以自动适应数据类型的变化。这种程序设计类型称为参数化程序设计(泛型程序设计)。 2019/2/19
模板是C++支持参数化程序设计的工具,通过它可以实现参数化多态性。 所谓的参数化多态性,就是将程序所处理的对象的类型参数化,使得一段程序可以处理多种不同类型的对象。 2019/2/19
函数模板和模板函数 请大家考虑这样的问题:写一个函数求两个值中的最大者。作为强类型的语言,C++ “不允许”也不应该两种不同类型的参数进行比较。一般的解决办法就是写一系列的函数,来分别完成整型、浮点型和用户自定义类型的求解。 int max(int x,int y){…} float max(float x,float y){…} 可以想像,所有这些函数(的代码)几乎都是一模一样的,只是操作的类型不同。这使程序代码变得累赘而加大维护难度。 2019/2/19
这样做虽然解决了代码维护问题,但是由于宏定义只是在编译时进行简单的宏展开,避开了类型检查机制,因此可能带来一些难以发觉的错误。 一个变通的方法是使用宏定义: #define max(x, y) ((x) > (y) ? (x) : (y)) 这样做虽然解决了代码维护问题,但是由于宏定义只是在编译时进行简单的宏展开,避开了类型检查机制,因此可能带来一些难以发觉的错误。 2019/2/19
使用C++的模板可以轻松地解决上述问题。在这种情况下,数据类型本身就是一个参数,例如max函数的模板可以定义为: template <class T> T max(T x, T y) { return x > y ? x : y; } 关键字template后的尖括号表明,max函数要用到一个叫做T的参数(我们称作模板参数),而这个参数是一种类型。该模板的含义就是无论参数T为int、char或其他数据类型(包括类类型),函数max的语意都是对x和y求最大值。 2019/2/19
这样定义的max代表了一类具有相同程序逻辑的函数,它不是一个真正的函数,被称为函数模板。函数模板本身是不被编译的,所以函数模板不能直接使用,必须被实例化后,也就是给定类型参数T后才能使用: void main() { double a = 1.0, b; b = max(a, 2.0); } 在上面的代码中,函数模板接受了一个隐含的参数:double,编译器自动将函数模板扩展成一个完整的关于double数据比较大小的函数,然后再在函数模板被调用的地方产生合适的函数调用代码。由函数模板实例化出的函数称为模板函数。 2019/2/19
就像类和对象的关系一样,函数模板将具有相同正文的一类函数抽象出来,可以适应任意类型T。 函数模板与模板函数的关系: 模板函数 max(int x,int y) 函数模板 max(T x,T y) max(double x,double y) max(X x,X y) 实例化 就像类和对象的关系一样,函数模板将具有相同正文的一类函数抽象出来,可以适应任意类型T。 2019/2/19
请思考 对于上述的max函数模板,如果参与比较的是两个类对象(如Complex类的对象),该怎么办? Complex c1,c2,c3; c3=max(c1,c2); 那么编译器将不能明白“>”运算符作用在类类型上是什么意思。在这种情况下,为了避免这个问题,必须为参与运算的类类型重载“>”运算符。 2019/2/19
重载模板函数 请思考下面情况: void Func(int num,char ch) { int a=max(num,ch);//错误 int b=max(ch,num);//错误 } 在这种情况下,为函数模板提供了两个不同的类型(int和char),那么这也会引起错误:编译器无法按模板的规则实例化出那样的函数。但是int和char直接的隐式类型转换是很普遍的。 解决上述问题的,C++允许一个函数模板可以使用多个模板参数或者重载一个函数模板。 2019/2/19
例子:使用多个模板参数 template <class T,class D> T max(T x,D y) { return (x>y)?x:y; } void main() int a=9; char b=34; int rr=max(a,b); 2019/2/19
例子:重载一个函数模板 //在redhat下使用g++编译并执行下面程序 template <class T> T max(T x,T y) { return (x>y)?x:y; } int max(int x,int y) void main() int num=1; char ch=2; max(num,num); //调用max(int,int) max(ch,ch); //调用max(T,T) max(num,ch); //调用max(int,int) max(ch,num); //调用max(int,int) 2019/2/19
类模板与模板类 请看下面双向链表的例子: class node { int value; node *prev,*next; public: node(){prev=NULL;next=NULL;} void setValue(int value){this->value=value;} void append(node *p); }; 2019/2/19
void node::append(node *p) { p->next=this->next; p->prev=this; if(next!=NULL) next->prev=p; next=p; } void main( ) { node *list_head; node node,node1,node2; node.setValue(1); node1.setValue(2); node2.setValue(3); list_head=&node; list_head->append(&node1); list_head->append(&node2); } 2019/2/19
类模板机制比较完美地解决了这个问题,我们将node类改造如下: 如果链表中的节点要保存char、double甚至是类类型的数据呢?该如何办?为了让该双向链表适应不同的类型,我们不得不写一系列的类,诸如int型node类、double型node类、以及类类型node类。而这些类除了操作的类型不同外,其它的部分都几乎一模一样。这对我们管理源代码带来极大的麻烦。 类模板机制比较完美地解决了这个问题,我们将node类改造如下: 2019/2/19
template <class T> class node { T value; node *prev,*next; public: node(){prev=NULL;next=NULL;} void setValue(T value){this->value=value;} void append(node *p); }; 2019/2/19
template <class T> void node<T>::append(node *p) { p->next=this->next; p->prev=this; if(next!=NULL) next->prev=p; next=p; } void main( ){ node<int> *list_head; node<int> node,node1,node2; node.setValue(1); node1.setValue(2); node2.setValue(3); list_head=&node; list_head->append(&node1); list_head->append(&node2); } 2019/2/19
用template来声明一个类模板,node<T>是该类模板的名字。 类外实现成员函数的语法为: template <class T> void node<T>::append(node *p){….} 用模板实参实例化的类称为模板类,在声明一个对象时完成类模板实例化的过程: node<double> *list_head; node<double> node,node1,node2; 2019/2/19
就像类和对象的关系一样,模板类将具有相同正文的一类类类型抽象出来。 类模板与模板类的关系: 模板类 node<int> 类模板 node<T> node<char> node<X> 实例化 就像类和对象的关系一样,模板类将具有相同正文的一类类类型抽象出来。 2019/2/19