详解C++中函数模板的定义与使用_第1页
详解C++中函数模板的定义与使用_第2页
详解C++中函数模板的定义与使用_第3页
详解C++中函数模板的定义与使用_第4页
详解C++中函数模板的定义与使用_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第详解C++中函数模板的定义与使用目录1.前言2.初识函数模板2.1语法2.2实例化2.3实参推导3.重载函数模板

1.前言

什么是函数模板?

理解什么是函数模板,须先搞清楚为什么需要函数模板。

如果现在有一个需求,要求编写一个求2个数字中最小数字的函数,这2个数字可以是int类型,可以是float类型,可以是所有可以进行比较的数据类型

常规编写方案:针对不同的数据类型编写不同的函数。

#includeiostream

usingnamespacestd;

//针对int类型

intgetMin(intnum1,intnum2){

returnnum1num2num2:num1;

//针对float类型

floatgetMin(floatnum1,floatnum2){

returnnum1num2num2:num1;

//针对double类型

doublegetMin(doublenum1,doublenum2){

returnnum1num2num2:num1;

intmain(){

//整型数据比较

intmin=getMin(10,4);

coutminendl;

//float类型数据比较

floatminf=getMin(3.8f,2.9f);

coutminfendl;

//double类型数据比较

doublemind=getMin(1.8,2.1);

coutmindendl;

return0;

重载函数(当然上述几个函数名也可以不相同)可以解决这个问题。显然,上述3个函数的算法逻辑是一模一样的,仅是函数的参数类型不一样。既然函数的形式参数可以接受值不同的同类型数据,能否把函数形参的数据类型也参数化,用来接受不同的数据类型。

函数模板实质就是参数化数据类型,称这种编程模式为数据类型泛化编程。

Tips:泛化的意思是一般化、抽象化,先不明确指定,需要时再指定。

如:我对班长说,我需要一名学生帮我搬课桌。这名学生到底是谁,我没有明确,由班长具体化。换在函数模板中,表示函数模板需要一种数据类型的数据,具体是什么数据类型,由使用者决定。

2.初识函数模板

2.1语法

在重构上述代码时,先了解一下函数模板的语法结构:

template模板形式参数列表返回类型函数名(函数形式参数列表)

{

函数体

}

语法结构说明:

1.template关键字说明了此函数是一个函数模板。

2.template的尖括号里是模板参数列表,也可称此处的参数为数据类型参数,用来对函数算法所针对的数据类型的泛化,表示可以接受不同的数据类型。

Tips:模板参数列表中的参数可以是一个或多个泛化数据类型参数,也可以是一个或多个具体数据类型参数。

泛化类型参数前面要加上typename关键字。

3.后面便是函数的一般性说明,只是在函数中可以使用模板数据类型参数。

Tips:函数模板中有2类参数,模板参数和函数参数。

使用函数模板重构上面求最小值的代码:

templatetypenameTTgetMin(Tnum1,Tnum2){

returnnum1num2num2:num1;

说明:

1.typenameT声明了一个数据类型参数,用于泛化任一种数据类型,或者说T可以表示任意一种数据类型。

Tips:typename是C++11标准,也可以使用class关键字,但建议不用,避免和类定义混淆。

2.T数据类型可以作为函数的参数类型、返回值类型、以及作为算法实施过程中临时变量的数据类型。

Tips:T是一个变量标识符,在遵循变量命名规则的前提下,可以起任意名称。

2.2实例化

函数模板如现实生活中制作陶瓷的模具一样,只有往模具中注入原材料,才能生成可实用的陶瓷。函数模板不是函数,仅是一个模板,不能直接调用,需要实例化后才能调用。

实例化:指编译器根据开发者对函数模板注入的具体(实参)数据类型构造出一个真正的函数实体(实例),这个过程由编译器自动完成,且实例化的函数对于开发者不可见。

intres=getMinint(1,6);

coutresendl;

//输出结果:1

如上,编译器通过函数模板内的int数据类型,实例化的函数可以对int类型的数据进行算法操作。同理,下面的代码会让编译器实例化针对不同数据类型的数据进行算法操作的函数。

//实例化原型为floatgetMin(floatnum1,floatnum2){函数体}的函数

floatresf=getMinfloat(3.2f,8.2f);

coutresfendl;

//实例化原型为doublegetMin(doublenum1,doublenum2){函数体}的函数

doubleresd=getMindouble(1.2,0.2);

coutresdendl;

//实例化原型为chargetMin(charnum1,charnum2){函数体}的函数

charresc=getMinchar('A','B');

coutrescendl;

//输出结果分别为3.2f0.2A

使用函数模板的优点不言而喻,声明一次,便可以实现针对不同数据类型的数据的操作。当然,中间会有匹配、实例化的代价。

Tips:高级业务层面的一劳永逸往往会以牺牲底层的性能为代价,但是,这是值得的。

除了通过显示声明数据类型提示编译器实例化,也可以使用函数指针实例化。

typedefint(*PF)(int,int);//1

PFpf=getMin;//2

intres=pf(6,8);//3

coutres;//4

说明:

1处先定义一个函数指针类型。

2处这行代码,千万不要理解是取函数模板的地址,编译器在底层做了相应处理。

编译器会根据函数指针类型说明先实例化一个函数。

再取实例化函数的内存地址,并赋值给pf。

3处以函数指针方式调用函数。

实例化时要注意的几个问题:

1.实例化时,可能会有一个直观问题:真的能指定任意一种数据类型实例化函数模板吗?

答案是:任何高级层面的逻辑行为都不能脱离基础知识的认知范畴,不同的数据类型有着语法系统赋予它的运算操作能力,当指定一个不支持函数模板内部算法操作的数据类型时,必然会出错。

如声明一个求2个数字相除的余数的函数模板。

templatetypenameTTgetYuShu(Tnum1,Tnum2){

returnnum1%num2;

如果指定double数据类型实例化getYuShu函数模板时,就会抛出错误,因为double数据类型不能使用%运算符。

doubleres=getYuShudouble(6.2,2.4);//出错

Tips:编译器在实例化函数模板时,会遵循语法标准检查给定的数据类型是否支持函数模板中的运算操作。

2.编译器实例化的时机。

常规而言,编译器会在程序中第一次需要函数模板的某个实例时对其进行编译。但是,同一份代码中,可能会出现对同一个实例多次调用的需要,如下面的代码:

templatetypenameTtest(Tnum){

returnnum;

intf(){

intres=testint(12);

returnres;

doublef1(){

intres=testint(24);

returndouble(res);

f和f1函数都需要使用testint实例,于编译器而,无法知道f和f1函数谁先会被调用(也就无法确定第一次编译的时间点),但为了保证编译期间完成实例化工作,早期C++编译器采用对同一实例每一次出现的地方都编译的策略,然后从多个编译结果中选一个作为最终结果,显然,编译时间会大大延长。

C++充许显式实例化声明,用来显示指定某一个函数模板的实例化的时间点,从而解决同一个实例被多次编译的问题。其语法如下:

template返回值类型模板名模板参数列表(函数形参列表);

针对上述函数模板可以编写如下代码,告之编译器编译时间点。

templatetypenameTtest(Tnum){

returnnum;

//显示指定实例化

templateinttestint(int);

Tips:显示声明只对一个源文件有效。

2.3实参推导

所谓实参推导,在使用函数模板时省略,不明确指定数据类型参数,而是由编译器根据函数的实参类型自动推导出类型参数的真正类型。如下代码:

intres=getMin(4,7);

实参是int类型,编译器由此推导出T是int类型,从而使用int类型实例化函数模板,类似于下面的显示声明代码:

intres=getMinint(4,7);

实参推导可以像调用普通函数一样使用函数模板。但是实参推导是有前提条件的:函数参数使用了类型参数的才能通过函数实参类型推导。如下的函数模板。

templatetypenameT1,typenameT2T2myMax(T1num1,T1num2){

//函数体

因为T2是作为函数模板的返回类型,是无法通过实参类型推导出来的。如下图所示:

使用如上函数模板,需要显示指定具体的数据类型。

doubleres=myMaxint,double(6,8);//正确

是否可以让函数模板的类型参数一部分显示指定,一部分由实参推导?

答案是可以,但是,要求在声明函数模板时,把需要显示指定的类型参数放在前面,可由实参推导的参数类型放在后面。把上面的函数模板的T1、T2参数说明交换位置。

templatetypenameT2,typenameT1T2myMax(T1num1,T1num2){

//函数体

实例化时,只需要显示指定T2的类型,T1类型由编译器根据实参推导。如下代码可正确调用。

doubleres=myMaxdouble(6,8);//正确

编译器把T2指定为double类型,然后根据实参6和8推导出T1是int类型。

了解什么是实参推导后,使用时,需要知道实参推导是不支持自动类型转换的。如下代码是错误的。

intres=getMin(4,7.5);//错误

编译器认定实参4是int类型,实参7.5是double类型,那么是到底是使用int类型还是使用double类型实例化getMin函数模板,会让编译器不知所措、左右为难。

Tips:即使支持自动类型转换,于编译器而言也无法知道开发者是想使用int类型还是double类型。如此自动类型转换没有存在的意义。

对于上述问题可以采用如下几种方案解决:

1.通过强制类型操作把实参转换成统一数据类型。

intres=getMin(4,int(7.5));

intres=getMin(double(4),7.5);

2.显示指定实例化时的数据类型。

intres=getMinint(4,7.5);

intres=getMindouble(4,7.5);

3.如果有必要传递2个不同类型的参数,可需要修改函数模板,使其能接受2种类型参数。

templatetypenameT1,typenameT2T1getMin(T1num1,T2num2){

returnnum1num2num2:num1;

3.重载函数模板

C++中普通函数和函数模板可以一起重载,面对多个重载函数,编译器需要提供相应的匹配策略。如下代码:

//普通函数

intgetMax(intnum1,intnum2){

returnnum1num2num1:num2;

//函数模板

templatetypenameTTgetMax(Tnum1,Tnum2){

returnnum1num2num1:num2;

如下调用时,编译器是选择普通函数还是函数模板?

intres=getMax(6,8);

函数实参是int类型,相比较函数模板,普通函数不需要实例化可直接使用,编译器会优先选择普通函数。但是如下的调用,编译器会选择函数模板。

getMax(2.4,6.8);//调用getMaxdouble(实参推导)

getMax('a','b');//调用getMaxchar(实参推导)

getMax(7,3)//调用getMaxint(实参推导)

getMaxdouble(4,9)//显示指定

编译器选择函数模板的原则:

如果函数模板能实例出一个完全与函数实参类型相匹配的函数,那么就会选择函数模板,如getMax(2.4,6.8);调用。编译器会根据函数模板实例化一个doublegetMax(doublea,doubleb)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论