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