C++程序设计第7章 函数模板教学课件.ppt

上传人:春哥&#****71; 文档编号:90589591 上传时间:2023-05-16 格式:PPT 页数:39 大小:2.19MB
返回 下载 相关 举报
C++程序设计第7章 函数模板教学课件.ppt_第1页
第1页 / 共39页
C++程序设计第7章 函数模板教学课件.ppt_第2页
第2页 / 共39页
点击查看更多>>
资源描述

《C++程序设计第7章 函数模板教学课件.ppt》由会员分享,可在线阅读,更多相关《C++程序设计第7章 函数模板教学课件.ppt(39页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、C+程序设计第7章 函数模板教学课件C C语言程序设计案例教程语言程序设计案例教程语言程序设计案例教程语言程序设计案例教程第7章 函数模板所谓函数模板,实际上是建立一个通用函数,用它对逻辑功能相同,但数据类型不同的一组函数进行统一描述,而其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。利用函数模板可以用一种逻辑过程处理不同类型的数据,从而提高编程的效率。http:/ 目录 7.1 定义函数模板 7.2 使用函数 7.4 函数模板的其他知识点3http:/ 7.1 定义函数模板定义函数模板与定义函数是相似的,其目的都是定义一个算法逻辑,只是一般函数的定义给出了具体的返回值类型和参数类型,

2、而函数模板的定义使用的则是虚拟的类型名,也即将其使用的类型进行了参数化。一般的函数往往只能处理一种数据类型,函数模板是为处理多种数据类型而定义的。7.1.1 使用函数模板的必要性在实际编程中,可能会遇到这样的问题:定义一个简单的求数组中最大值的函数,要求可以处理各种数据类型。学习了函数重载(4.7节)后,我们可能希望定义几个这样的函数,每一个可以比较一种给定类型的值,即第一次尝试可能是定义几个重载函数。4http:/ 3这里只写出了针对int型和float型数组的函数。为了适应各种情形,还应当编写针对double、char、short、long等数据类型的函数。除此之外,还要支持自定义数据类型

3、(如结构体、类等)。可见,这种面面俱到的方式虽能解决问题,但真正实施起来,不仅麻烦而且容易出错。一方面,由于每个要比较的类型都需要重复函数的函数体,程序的代码量会大大增加;另一方面,一旦算法逻辑发生改变,或者要纠正某个错误,所有的重载函数都需要作相同的修改。例如:int max(int ary,int n)/整型数组求最大值 int max_val=0;/保存最大值的变量 for(int i=0;iaryi?max_val:aryi;/记录最大值return max_val;/返回最大值float max(float ary,int n)/float型数组求最大值 float max_val=

4、0.0;/以下的逻辑过程同上面求整型数组中最大值的逻辑一致 for(int i=0;iaryi?max_val:aryi;/记录最大值return max_val;/返回最大值http:/ 经过分析可知,这些函数在算法逻辑上都是一样的,即每个函数的函数体是相同的。它们之间唯一的区别是所处理数据的类型不同。鉴于这种情况,C+语言提供了函数模板。函数模板将函数的类型进行参数化处理,使得只编写一次通用算法逻辑就可以处理所有的数据类型,而不必为每个类型定义一个新函数。例如,上面的例子就可以用函数模板改写为如下的形式(如何定义并使用函数模板将在后面的章节中讲解)。template /定义函数模板,T是类

5、型变量,代表某种类型T max(T ary,int n)/使用类型T定义函数的返回值和参数类型 T max_val=0;/使用类型T声明临时变量 for(int i=0;i aryi?max_val:aryi;/记录最大值return max_val;/返回最大值可以看到,通过使用函数模板,避免了相同逻辑的无意义重复,简化了程序,同时也减轻了程序设计者维护程序的工作量,提高了开发效率。5http:/ 7.1.2 抽取通用算法逻辑定义函数模板,通常是出于这样的目的:定义一个适用于各种数据类型的通用算法。例如,定义一个表示两个数相减的函数,要求传入两个任意类型的参数,最终返回这两个参数的差。由前面

6、的介绍可知,利用函数模板,对函数类型进行参数化处理就能达到目的。但事实上,函数类型(即返回类型和参数类型)并不是问题的核心,问题的关键是减法(算法)。只要对常见的数据类型,如整型、浮点型等,都使用减法运算符“-”即可。所以这里的通用算法逻辑就是两个参数相减,并返回结果。抽取通用算法逻辑的过程如图7-1所示。6图7-1 抽取通用算法逻辑图7-1是以类型作为模板参数的,实际上,常量也可以作为模板参数,例如:template /定义函数模板,T是类型变量,size为非类型参数这里将数组的大小size作为模板参数。http:/ 7.1.3 函数模板定义的语法在C+中,函数模板的定义以关键字templa

7、te开始,接着在符号“”之间给出函数的模板参数列表,该列表不能为空。模板参数可以是表示类型的类型参数,也可以是表示常量表达式的非类型参数。定义函数模板的语法如下。template 返回类型 函数名(函数参数列表)函数体例如,前面求数组中最大值的函数模板的定义如下。template T max(T ary,int n)7http:/ 1)模板形参模板参数列表中可以包含一个或多个类型参数,也可以是一个或多个非类型参数,甚至可以是两种参数的混合。只是在有多个模板参数时,必须用逗号隔开,并且该列表不能为空。非类型参数跟在类型说明符之后,就像声明普通参数一样声明;类型参数跟在关键字typename或cl

8、ass之后定义。例如:template 上述模板参数列表中就声明了两个类型参数和两个非类型参数。其中,两个类型参数T1和T2,代表两种数据类型,可以是自定义类型,也可以是内置类型;两个非类型参数num1和num2,其声明表示该模板在定义过程中可以将它们当做常量使用。注意:类型参数也是一个标识符,应当遵循标识符的命名规则,即只能由数字、字母和下划线组成,并且不能以数字开头。关键字typename和关键字class的作用相同,都是表示它后面的参数名代表一个潜在的内置或用户自定义的类型,二者可以互换。但这里的class容易与类的声明(将在9.1.1中介绍)关键字class混淆。虽然它们都由相同的字母

9、组成,但却代表不同的含义。为了区别类与模板参数中的类型关键字class,标准C+提出了用typename作为模板参数的类型关键字,同时也支持使用class。9http:/ 如果用typename,其含义就很清楚,肯定是类型名而不是类名。在函数参数列表以及函数体的定义过程中,可以使用模板参数列表中的参数。如果使用类型参数,可以用该类型声明函数的返回值、形参、临时变量,例如:template /定义函数模板,类型参数TT max(T ary,int n)/使用类型参数T声明函数的返回值、形参类型 函数体如果使用非类型参数,那么可以在函数体的定义中将其当做常量使用,也可以将其当做函数的默认实参,例如

10、:template /定义函数模板,非类型参数sizeT max(T ary,int n=size)/使用非类型参数size,将size默认为数组的长度 函数体10http:/ 2)参数化的函数定义模板参数列表之后紧接着就是一个参数化的函数定义。这里所谓的参数化,主要是指对函数返回值类型、形参类型以及函数体中常量类型的参数化。例如,定义一个求数组中最大值的函数模板,对于不同的数据类型(如整型、浮点型)其算法逻辑都是一样的,不一样的只是函数返回值及参数的类型,因此可以将这两部分参数化。【例7-1】用函数模板求数组中的最大值。#include using namespace std;templat

11、e /定义函数模板,T为类型参数T max(T ary,int n)/T型数组求最大值 T max_val=0;/保存最大值的变量 for(int i=0;imax_val)/如果当前元素比当前最大值大 max_val=aryi;/修改保存最大值变量return max_val;/返回最大值void main()/理解函数模板11http:/ int a14=2,8,4,6;/定义整型数组并初始化 int i=max(a1,4);/调用函数模板,求整型数组的最大值,此时T被int取代 double a24=2.0,1.2,4.1,3.4;/定义浮点型数组并初始化 double d=max(a2

12、,4);/调用函数模板,此时T被double取代 cout i_max=i endl;cout d_max=d endl;【例7-1】的第一行定义了一个函数模板,类型参数T。在第二行的函数模板max的定义中,第一个T是函数返回值类型的参数,第二个T和后面的ary 表示T类型的数组形参。第四行的T用来在函数体中定义一个类型为T的变量。T是max模板的模板参数,可以用来表示多种数据类型,只是此时还未确定,要到模板max被使用时才能确定。编译调试,程序运行结果如图7-2所示。图7-2 利用函数模板求数组中的最大值12http:/ 12需要说明如下几点。需要说明如下几点。(1)在定义函数模板时,不允许

13、template语句与参数化的函数(函数模板)之间有任何其他语句。下面的模板定义是错误的。template int i;/错误,不允许在此位置有任何语句 T max(T ary,int n)(2)模板的类型参数T与具体类型的关系有点像变量和变量值的关系。T是一个类型变量,其类型就是“类型”,而其值就是某种具体类型,如int、double等。(3)参数名T由程序员定义,其实也可以不用T而用任何一个标识符,许多人习惯用T(T是Type的第一个字母),而且用大写,以与实际的类型名相区别。(4)可以用与非模板函数一样的方式声明函数模板为inline。只是inline说明符应放在模板形参表之后、返回类型

14、之前,不能放在关键字template之前。例如:template inline T max(T,int);/正确inline template T max(T,int);/错误http:/ 7.1.4 使用非类型参数前面已经提到,模板参数可以是一个类型参数,代表一种类型;也可以是一个非类型参数,代表一个常量。使用非类型参数的目的就是为函数引入一个常量,以便在定义函数时当做常量或默认实参使用。例如,对于求一个数组的最大值问题,可以用一个非类型参数表示数组长度。下面的代码就是使用非类型参数来定义函数模板。template /定义函数模板,类型参数为T,非类型参数为sizeT max(T ary,i

15、nt n=size)/接受类型为T的数组及默认为size的数组长度 T max_val=0;/定义T类型的变量max_val for(int i=0;imax_val)/如果当前元素比最大值还大 max_val=aryi;/修改最大值return max_val;/返回最大值程序的第一行就定义了一个非类型参数size,用以表示数组的长度。注意:C+标准规定模板参数必须在程序编译时确定。因此非类型的模板参数的值必须是一个在程序编译时就能确定的常量(包括字面常量、const符号常量等)13http:/ 7.2 使用函数由于函数模板不是真正的函数,而只是对逻辑功能相同但数据类型不同的一组函数的统一描

16、述,因此使用函数模板不是简单的函数调用。在使用函数模板之前,必须先将模板的参数替换为具体的数据类型。在C+中,函数模板参数的替换可以由用户明确指定,也可以由编译器自行推演。7.2.1 实例化函数模板在程序中使用函数模板,实际上是使用该模板的实例。在程序编译过程中,编译器根据函数调用中提供的函数实参类型推演出具体的模板参数,然后用这些模板参数实例化模板,并将产生的实例编译成具体的机器码。一旦编译器确定了实际的模板参数,我们就称它实例化了函数模板的一个实例实质上,编译器承担了为我们使用的各种类型而编写函数的单调、重复工作。编译器将确定用什么类型代替每个类型形参,以及用什么值代替每个非类型形参。例如

17、,对于如下所示的函数模板:template T max(T arysize)T max_val=ary0;for(int i=1;imax_val)/如果当前元素比最大值还大 max_val=aryi;/修改最大值14http:/ return max_val;/返回最大值如果在main函数中有以下语句:int main()int i_ary4=8,5,12,3;int i=max(i_ary);double d_ary6=12.3,9.8,13.4,6.7,26.0,5.3;double d=max(d_ary);编译器将实例化函数模板max的两个不同的版本,它将用int代替T创建第一个版本

18、,并用double代替T创建第二个版本。在上述代码中,main函数第三行定义了一个含4个元素的整型数组,当程序执行到第四行时,编译器根据函数max的实参决定模板实参size的值和T的类型。分别是4和int。同理,main函数第五行定义了一个含6个元素的浮点型数组,当程序执行到第六行时,编译器又会根据函数max的实参决定模板实参size的值和T的类型。分别是6和double。只有当编译器遇到程序中对函数模板的调用时,它才会根据调用语句中实参的具体类型,确定模板参数的数据类型和参数值,并用此类型和相应的值替换函数模板中的模板参数,生成能够处理该类型的函数代码,即模板实例。16http:/ 16图7

19、-3 函数模板与模板函数的关系为帮助理解,下面定义一个函数模板,用于查找一个数组中的某个值。如果找到了,就返回该值在数组中的位置;如果找不到,则返回-1。http:/ 17【例7-2】实例化函数模板。#include using namespace std;template /定义函数模板,包括类型参数T和数值参数sizeint search(T(&ary)size,T ele)/定义查找函数:在T类型数组ary中查找元素ele for(int i=0;isize;i+)/遍历数组 if(ele=aryi)/如果找到 return i;/返回元素当前位置return-1;/未找到则返回-1in

20、t main()/实例化函数模板 int i_ary6=1,2,3,4,5,6;/定义含6个元素的整型数组 double d_ary5=1.1,2.2,3.3,4.4,5.5;/定义含5个元素的浮点型数组 cout 整数4的位置:search(i_ary,4)endl;/在i_ary中查找4 cout 整数12的位置:search(i_ary,12)endl;/在i_ary中查找12 cout 浮点数2.2的位置:search(d_ary,2.2)endl;/在d_ary中查找2.2 cout 浮点数9.9的位置:search(d_ary,9.9)y?x:y);上述代码中的第4行定义了一个指向

21、函数的指针变量,其中值得注意的是,int和“(”之间可以存在空格也可以不存在空格。正如取一般函数的地址一样,在取函数模板的地址前也需要先定义一个指向该模板实例的函数指针,以便把所取得的地址赋给该指针变量。该函数指针的类型就是用来实例化函数模板的参数。18http:/ 7.2.3 函数模板实参的推演函数模板不是真正的函数,在使用前必须先实例化。因而,在调用函数模板(实际上是调用实例化得到的函数)的过程中必然存在一个实例化的过程,而函数模板的实例化的先决条件是求得模板实参。1)函数模板实参的推演过程当函数模板被调用时,从函数实参确定模板实参的类型和值的过程叫做模板实参推演。函数模板实参推演大致分两

22、步进行。(1)编译时,编译器依次检查每个函数实参,以确定在每个函数实参的类型中出现的模板参数。(2)如果找到模板参数,则通过检查函数实参的类型,推演出相应的模板实参的类型或值。例如,对于下面的程序:template int search(T arysize,T ele);/声明查找函数模板:在T类型数组ary中查找元素eleint i_ary6=1,2,3,4,5,6;int i=search(i_ary,4);/在数组i_ary中查找419http:/ 程序中给出了一个函数模板search,它的返回值类型为int,接受两个参数,即大小为size、类型为T的数组和类型为T的用以保存要查找的数的

23、变量。第3行定义了一个含6个元素的整形数组,第4行是函数模板search的调用,传入的实参分别是数组i_ary和4。该函数模板调用的实参推演过程如下。(1)检查函数实参i_ary和4,确定它们的类型都为int型。(2)根据函数模板的声明及步骤(1)的查找,通过实参数组i_ary得到第一个模板类型参数T的值是int型,以此类推,通过实参4得到第二个模板类型参数T的值也是int型,同时,数组i_ary的大小是6,所以模板实参size的值是6。需要说明的是:(1)由于在C+语言中,函数调用可以不取返回值,所以在函数模板实参推演的过程中,函数的返回值类型并不参与其中。试想,既然程序中没有返回值信息,那

24、么也无法根据返回值类型来进行实参推演了。(2)函数模板实参推演的过程中,函数的形参和实参的类型不必完全匹配,只要能够将实参转换为形参的类型,即实参与形参之间存在自动的类型隐式转换即可。多个函数实参可以参加同一个模板实参的推演过程。如果模板实参在函数参数表中出现多次,则每个推演出来的类型都必须相同。如果推演的类型不相同,则调用将会出错,20http:/ 例如:template int max(T x,T y)return(xy?x:y);int main()short a;int b;max(a,b);return 0;程序中第10行的调用是错误的,因为调用max时的实参类型不相同,从第一个实参

25、推演出来的模板实参是short,而从第二个实参推演出来的是int,两个类型不匹配,所以模板实参推演失败。针对上面的问题,可以用两个类型形参来定义函数模板max,例如:template int max(T1 x,T2 y)return(xy?x:y);这样就可以提供不同类型的实参了,例如:short a;int b;max(a,b);虽然从第一个实参推演出来的模板实参是short,从第二个实参推演出来的是int,但因为它们对应的是不同的模板形参,所以模板实参推演成功。21http:/ 2)实参推演中的类型转换上面的内容曾讲到:在函数模板实参推演的过程中,函数的形参和实参的类型不必完全匹配,只要能

26、够将实参转换为形参的类型,即实参与形参之间存在自动的类型隐式转换即可。所以接下来将要介绍的2种类型转换是允许的,它们是左值转换和限定符修饰转换。(1)左值转换。在介绍左值转换之前有必要先简要介绍一下左值与右值的相关知识。其实,左值和右值是相对于赋值表达式而言的。左值是指出现在赋值表达式左边的表达式,左值表达式不但具有空间实体,而且具有一个相关联的、可写的地址值,如变量、引用就是左值。右值是可以出现在赋值表达式右边的表达式,也就是可以从中读取数据的程序实体,同时它也可以是不占据内存空间的临时量或字面量,如各种常量、表达式等就是右值,此外,变量也可以是右值。左值和右值之间存在如下关系:左值=右值左

27、值转换包括从左值到右值的转换、从数组名到指针的转换,以及从函数名到函数指针的转换。这里所说的转换,是指将左值当做右值使用,而不是说将左值转变成右值。事实上,在C+语言中所有的左值都可以作为右值,即所有的左值都可以从中读取数据,而这个读取的过程就是所谓的从左值到右值的转换。需要说明如下两点:数组名和函数名是右值,而指针和函数指针是左值。数组名和函数名都可转换为相应的指 被关键字const修饰的符号常量是右值,而普通的变量则是左值。普通变量可以转换为相应的符号常量。22http:/ using namespace std;template /定义函数模板,T1、T2是两个类型参数void sum(

28、const T1 x,T2*y)/接受类型为T1的符号常量及类型为T2的指针变量 T1 sum=0;for(int i=0;ix;i+)sum+=yi;/数组求和cout sum=sum endl;/输出结果int main()int a=4;/普通整型变量 double ary=2.0,4.0,6.0,8.0;/双精度浮点型数组 cout /*数组求和*/endl;sum(a,ary);/调用函数模板sumreturn 0;23分析main函数中的函数模板sum的调用可知,实参a是一个普通的整形变量,是一个左值,它对应模板sum的第一个参数x(一个被const修饰的右值)。因为左值都可以转换

29、为右值,所以可以推定T1的类型为int型。同样,对于第二个数组实参ary,由于数组可以转换为指针,所以可以推定T2的类型是double型。程序运行结果如图7-6所示。图7-6 理解左值转换http:/ 7.2.4 显示指定函数模板的实参考虑下面的问题:我们希望定义一个名为add,接受两个不同类型实参的函数模板,要求返回值足够大,可以包含任意两个类型的两个值的和,怎样才能做到?应如何指定add的返回值类型是什么?要解决此类问题,就需要显示指定函数模板的实参,强制add的调用者将较小的类型强制转换为希望作为结果使用的类型,如下面的程序。template T add(T,T)short a;int

30、b;add(a,b);上述程序的最后一行,通过模板实参表强制将函数模板add的实参a、b指定为int型。虽然函数调用中的第一个实参a是short型,但是通过显示模板实参就已经推定函数的参数类型为int型,所以参数a的类型会被转换为int型。显示指定函数模板的实参也称为显示实例化,其作用主要是解决模板实参推演时的二义性问题。既然指定了模板实参,那么在使用函数模板时就不必进行实参推演了,也就避免了实参推演的二义性问题。24http:/ 7.4 函数模板的其他知识点在前面,我们较为详细地介绍了如何定义以及使用函数模板,下面进一步讲解在定义及使用函数模板的过程中应当注意的语法规则以及一些细节问题。7.

31、4.1 函数模板的编译模板的编译不同于普通的C+语言,它比较特殊,不像普通的语言特征那样即刻就可以编译出相应的机器码。函数模板定义的仅仅是一个参数化的逻辑过程,当编译器看到模板定义时,它并不立即产生代码。只有在看到调用函数模板时,编译器才产生特定类型的模板实例。一般而言,当调用普通函数时,编译器只需要看到函数的声明。因而,应该将函数声明放在头文件中,而将函数的定义放在源文件中。模板则不同,要进行实例化,编译器就必须能够访问定义模板的源代码。当调用函数模板时,编译器需要函数定义,需要那些通常放在源文件中的代码。标准C+为编译模板代码定义了包含编译和分别编译两种模型。1)包含编译模型在包含编译模型

32、中,由于编译器必须看到需要用到的所有模板的定义,因此模板通常在头文件中定义。这样,如果某个源文件需要实例化模板,就包含模板所在的头文件即可。25http:/ 例如:/*template.h 头文件*/template /包含编译模型:模板定义在头文件中T max(T x,T y)if(xy)return x;else return y;在每个需要使用max实例的文件中都要使用#include命令包含该头文件。例如:/*function.cpp 源文件*/#include template.h /在使用模板实例之前包含模板定义的头文件void main()int a,b,c;c=max(a,b)

33、;这一策略使得程序设计者能够保持头文件与实现文件的分离。2)分别编译模型 所谓分别编译模型,是指将函数模板的声明放在头文件中,而把模板定义放在另外的源文件中,如下面的例子。头文件中只是函数模板的声明。/*template.h 头文件*/template /分别编译模型:头文件中只提供模板声明T max(T x,T y);在源文件中定义函数模板。/*template.cpp 源文件*/export template /模板定义T max(T x,T y)if(xy)return x;else return y;图6-8 delete关键字的使用26http:/ 使用函数模板max实例的程序只需在

34、使用该实例之前用#include命令包含相应的头文件。/*function.cpp 源文件*/#include template.h /在使用模板实例之前包含模板定义的头文件void main()int a,b,c;c=max(a,b);想必读者已经注意到,在存放模板定义的源文件中,在关键字template前加上了export关键字。因为在分别编译模型中,编译器会为程序设计者跟踪相关的模板定义,但前提是程序设计者必须告知编译器,让它记住给定的模板定义,这时就需要用到关键字export。3)两种模板的异同包含编译模型和分别编译模型构造程序的方式在很大程度上是相同的,如函数模板声明放在头文件中,而

35、函数模板定义放在源文件中;两者的不同之处主要在于编译器怎样使用来自源文件的定义。27http:/ 7.4.2 函数模板定义中的标识符解析在函数模板的定义中,除了会用到前面学过的一些常见标识符外,还可能会用到用户自定义的函数或函数模板。C+语言中的标识符解析是指,编译器在处理函数模板的定义时,确定被调用的函数或使用的函数模板实例的过程。C+标准规定,编译器对模板定义中的标识符解析分两步进行。(1)不依赖于模板参数的标识符在模板定义时被解析。(2)依赖于模板参数的标识符在模板实例化时被解析。例如,下面所列举的例子中定义了一个函数模板tem,该模板的定义中又调用了函数add来实现两个数相加。但是被调

36、用函数add已被重载,因此编译器在处理模板tem的定义时,就应当进行必要的标识符解析。【例7-5】理解函数模板定义中的标识符解析。#include using namespace std;void add(int x,int y)/求两个整数之和的函数定义 /注意该定义必须放在模板tem定义之前,否则编译将会出错cout x+y;template void tem(T1 t1,T2 t2)/模板tem,接受两个类型参数add(3,9);/调用函数,实现两个整数相加cout endl;add(t1,t2);/调用函数,实现两个未知类型的数相加。该函数定义可以放在cout endl;/模板tem的

37、定义之前或之后,但必须放在tem(2,3.67)之前28http:/ void add(int a,double b)/求两个浮点数之和的函数定义cout a+b;int main()tem(2,3.67);/参数分别为整型和浮点型,调用模板temreturn 0;上述程序在模板tem的定义中调用了两次函数add。其中,第一次调用给出了参数的具体类型(int型),第二次调用给出的是参数化的函数模板tem的两个形参,其类型未知,依赖于tem的模板实参。程序运行结果如图7-7所示。图7-7 函数模板中的标识符解析29在【例7-5】中,由于第一次调用所引用的add重载版本是确定的,所以其相关定义必须

38、出现在模板tem的定义之前,否则会导致编译错误。第二次调用所引用的add重载版本并未确定,依赖于模板实参,所以其相关定义必须出现在tem的相关实例之前。http:/ 7.4.3 函数模板的特化在实际编程中,编写出适合所有可能被实例化的类型的函数模板是相当困难的。这主要是因为对于某种逻辑操作,各种数据类型的实现并不一定相同。例如,整型或浮点型数的相加,只需调用加法运算符“+”即可,但对于字符串的相加,直接使用加法运算符就显得毫无意义了,而是需要另外调用库函数strcat来实现多个字符串的连接。针对这种情况,我们可以用函数模板的特化来处理。现有函数模板add的定义如下。template T add

39、(T t1,T t2)return t1+t2;如果用两个const char*型实参调用这个模板定义,函数将处理指针值,它将返回两个指针的地址相加后的值,而程序员的本意是将两个字符串相加。为了能够将add函数用于字符串,必须针对const char*类型,为函数模板add提供特化的版本。函数模板特化是指一个或多个函数模板形参的实际类型或实际值是指定的。函数模板特化的步骤如下。(1)关键字template和后面的一对尖括号“”。(2)再接函数模板特化的返回值类型和模板名以及一对尖括号,尖括号中指定这个特化定义的模板参数。(3)函数形参表。(4)函数体。30http:/ 函数模板特化的语法格式如

40、下。/*模板特化定义*/template 特化返回类型 模板名(函数形参列表)函数体那么,针对const char*类型,模板add显示特化为:template /针对const char*类型特化模板addconst char*add(const char*s1,const char*s2)return strcat(s1,s2);/调用库函数strcat,连接字符串 现在,当调用add函数时,如果传给它两个字符指针,编译器将调用特化版本。而对于其他任意实参类型(包括普通char*),编译器将调用通用版本。说明:对于特化模板中的“模板实参列表”,如果函数模板的“特化返回类型”与“函数形参”的

41、类型相同,那么该列表也可以省略。例如,上例中形参的类型和返回值类型都是const char*,所以上述特化模板也可以写成template const char*add(consr char*s1,const char*s2)。31http:/ 7.4 函数模板的其他知识点在前面,我们较为详细地介绍了如何定义以及使用函数模板,下面进一步讲解在定义及使用函数模板的过程中应当注意的语法规则以及一些细节问题。7.4.1 函数模板的编译模板的编译不同于普通的C+语言,它比较特殊,不像普通的语言特征那样即刻就可以编译出相应的机器码。函数模板定义的仅仅是一个参数化的逻辑过程,当编译器看到模板定义时,它并不立

42、即产生代码。只有在看到调用函数模板时,编译器才产生特定类型的模板实例。一般而言,当调用普通函数时,编译器只需要看到函数的声明。因而,应该将函数声明放在头文件中,而将函数的定义放在源文件中。模板则不同,要进行实例化,编译器就必须能够访问定义模板的源代码。当调用函数模板时,编译器需要函数定义,需要那些通常放在源文件中的代码。标准C+为编译模板代码定义了包含编译和分别编译两种模型。1)包含编译模型在包含编译模型中,由于编译器必须看到需要用到的所有模板的定义,因此模板通常在头文件中定义。这样,如果某个源文件需要实例化模板,就包含模板所在的头文件即可。32http:/ 7.4.4 函数模板的重载在第4章

43、中,讲解了普通函数的重载。和普通函数一样,函数模板也可以重载:可以定义有相同名字但形参类型或数目不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数(这里只介绍第一种情况,第二种情况将在后面详细介绍)。例如,图7-9中就定义了两组重载的函数模板。33template T max(T)template T max(T,T)template T max(T*,int)template T max(T,T)(a)第一组 (b)第二组 图7-9 函数模板重载示例可以看到:重载的两组函数模板中,第一组中的两个模板的模板形参、模板名称相同,只是函数形参的类型不同;同样,第二组中的两个模板的模

44、板形参、模板名称也相同,不同的是函数形参的个数。http:/ 假设现有针对第一组重载函数模板的调用:void main()int ary3;int i_ary=max(ary,3);/T推定为int double d=max(2.0,3.0);/T推定为double 上述例子中main函数的第4行,调用给定的第二个实参是整型数3,所以更适合第一个函数模板。第5行的模板调用给出了两个浮点型常量,更适合第二个函数模板。其实,在使用重载函数模板的过程中,如果存在多个候选模板,那么在实参推演过程中,编译器通常会选择函数实参与形参类型相近的模板。使用重载函数模板的方法与使用重载函数相似,在此不再过多讲述

45、。二者较为明显的区别是:重载函数不存在二义性问题,而重载函数模板则可能导致二义性。例如:/*函数模板1*/template int max(T,T)/*函数模板2*/template int max(T1,T2)模板1与模板2是重载的,如果出现如下调用语句,将会导致二义性。max(3,5);调用语句的实参是两个整型数,所以肯定适合于模板1;但又由于模板2中的类型参数T1和T2可以相同,所以此调用同样适合于模板2。因此,在进行程序设计时,应当尽量避免采用重载函数模板,以避免不小心带来的二义性问题。34http:/ 7.4.5 函数匹配规则前面在介绍函数模板的重载时曾提到过:可以定义与函数模板有相

46、同名字的普通非模板函数。在C+语言中,函数模板是可以与普通函数同名的,即重载函数中既可以有普通函数,也可以有函数模板。只是在这种情况下,当出现函数调用时,需要编译器判断是用普通非模板函数,还是函数模板实例。如果重载函数中既有普通函数又有函数模板,那么编译器的处理规则是:非模板函数具有最高的优先权,如果不存在匹配的非模板函数,则最匹配的和最特化的函数具有最优先权。确定函数调用的具体步骤如下。(1)建立候选函数集合,包括:与被调用函数名字相同的所有普通函数;任意函数模板实例化,其中,模板实参推断发现了与调用中所用函数实参相匹配的模板实参。(2)确定可行函数。确定普通可行函数(如果有的话);候选集合

47、中的每个模板实例都是可行的,因为模板实参推断已保证函数可以被调用。(3)如果需要转换来进行调用,根据转换的种类排列可行函数。如果只有一个函数可选,就调用这个函数。如果调用具有二义性,从可行函数集合中去掉所有函数模板实例。(4)重新排列去掉函数模板实例的可行函数。如果只有一个函数可选,就调用这个函数。否则调用具有二义性。不过这一系列操作都是由编译器来完成的,编译器承担了为我们编写各种类型函数的单调繁琐工作,程序员只需提供相应的指令。31http:/ 在在main函数中有如下调用。函数中有如下调用。int i;char c;double d;f(c,3);/调用重载函数调用重载函数1,T=char

48、f(&i);/调用重载函数调用重载函数2,T=int*f(i,d,6);/调用重载函数调用重载函数3,T=intf(d);/调用重载函数调用重载函数5如果有如下重载函数。如果有如下重载函数。template void f(T,int);/重载函数重载函数1template void f(T*);/重载函数重载函数2template void f(int,double,T);/重载函数重载函数3template void f(int);/重载函数重载函数4void f(double);/重载函数重载函数535Thanks第7章 函数模板C C语言程序设计案例教程语言程序设计案例教程语言程序设计案例教程语言程序设计案例教程

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 大学资料

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com