模版深入详解.docx
《模版深入详解.docx》由会员分享,可在线阅读,更多相关《模版深入详解.docx(45页珍藏版)》请在冰豆网上搜索。
![模版深入详解.docx](https://file1.bdocx.com/fileroot1/2022-11/20/fc875778-80f6-4f21-a716-29e9ca120efe/fc875778-80f6-4f21-a716-29e9ca120efe1.gif)
模版深入详解
在C++发明阶段,C++之父Stroustrup和贝尔实验室的C++小组对原先的宏方法进行了修订,对其进行了简化并将它从预处理范围移入了编译器。
这种新的代码替换装置被称为模板,而且它变现了完全不同的代码重用方法:
模板对源代码重用,而不是通过继承和组合重用对象代码。
当用户使用模板时,参数由编译器来替换,这非常像原来的宏方法,却更清晰,更容易使用。
模板使类和函数可在编译时定义所需处理和返回的数据类型,一个模板并非一个实实在在的类或函数,仅仅是一个类和函数的描述。
由于模板可以实现逻辑相同、数据类型不同的程序代码复制,所以使用模板机制可以减轻编程和维护的工作量和难度。
模板一般分为模板函数和类模板。
以所处理的数据类型的说明作为参数的类就叫类模板,或者模板类,而以所处理的数据类型的说明作为参数的函数,则称为函数模板。
本文包含函数目标和类模板,有些可能会交错设计两个模块的细节。
1.函数模板
函数模板定义了参数化的非成员函数,这使得程序员能够用不同类型的参数调用相同的函数,由编译器决定调用哪一种类型,并且从模板中生成相应的代码。
定义:
Template﹤类型参数表﹥返回类型函数名(形参表){函数体}
简单实例,调用函数打印字符串或数字等。
普通函数形式:
1.#include
2.#include
3.voidprintstring(conststd:
:
string&str)
4.{
5.std:
:
cout<:
endl;
6.}
7.intmain()
8.{
9.std:
:
stringstr("HelloWorld");
10.printstring(str);
11.return0;
12.}//输出:
HelloWorld
模板函数形式:
1.#include
2.#include
3.usingnamespacestd;
4.templatevoidprint(constT&var)
5.{
6.cout<7.}
8.intmain()
9.{
10.stringstr("HelloWorld");
11.constintnum=1234;
12.print(str);
13.print(num);
14.return0;
15.}
16.//输出:
HelloWorld
17.//1234
可以看出使用模板后的函数不仅可以输出字符串形式还可以输出数字形式的内容。
上面两个例子介绍了函数模板的简单使用方法,但只有一个参数,如果需要多个参数,相应的函数模板应采用以下形式定义:
Template﹤类型1变量1,类型2变量2,…﹥返回类型函数名(形参表){函数体}
现在,为了看到模板时如何称为函数的,我们假定min()函数接受各种类型的参数,并找出其中的最小者,如果不采用模板技术,则只能接受一个特定类型的参数,如果希望也能接受其他类型的参数,就需要对每一种类型的参数都定义一个同功能的函数,其实为函数的重载,这里不在讨论,但这将是一件非常让人麻烦的事情。
如:
普通定义:
1.#include
2.//定义多态函数,找出三个整数中最小的数
3.intmin0(intii,intjj,intkk)
4.{
5.inttemp;
6.if((ii7.elseif((jj8.else{temp=kk;}
9.returntemp;
10.}
11.//定义多态函数,找出三个小数中最小的数
12.floatmin1(floatii,floatjj,floatkk)
13.{
14.floattemp;
15.if((ii16.elseif((jj17.else{temp=kk;}
18.returntemp;
19.}
20.
21.//定义多态函数,找出三个子符中最小的字符
22.charmin2(charii,charjj,charkk)
23.{
24.chartemp;
25.if((ii26.elseif((jj27.else{temp=kk;}
28.returntemp;
29.}
30.
31.voidmain()
32.{
33.inttemp1=min0(100,20,30);
34.cout<35.floattemp2=min1(10.60,10.64,53.21);
36.cout<37.chartemp3=min2('c','a','C');
38.cout<39.}
40.//以换行形式输出2010.6C
使用模板:
1.#include
2.//定义函数模板,找出三个值中最小的值,与数据类型无关
3.template
4.Tmin(Tii,Tjj,Tkk)
5.{
6.Ttemp;
7.if((ii8.elseif((jj9.else{temp=kk;}
10.returntemp;
11.}
12.//下面是主函数
13.voidmain()
14.{
15.cout<16.cout<17.cout<18.}
输出结果同上,但可以清楚的看到二者之间的工作量大小之差距。
函数模板功能非常强大,但是有时候可能会陷入困境,加入待比较的函数模板没有提供正确的操作符,则程序不会对此进行编译。
为了避免这种错误,可以使用函数模板和同名的非模板函数重载,这就是函数定制。
函数模板与同名的非模板函数重载必须遵守以下规定:
寻找一个参数完全匹配的函数,如有,则调用它
如果失败,寻找一个函数模板,使其实例化,产生一个匹配的模板函数,若有,则调用它
如果失败,再试低一级的对函数重载的方法,例如通过类型转换可产生的参数匹配等,若找到匹配的函数,调用它
如果失败,则证明这是一个错误的调用
现在用上例的模板函数比较两个字符串,但会出现问题:
1.#include
2.//定义函数模板,找出三个值中最小的值,与数据类型无关
3.template
4.Tmin(Tii,Tjj,Tkk)
5.{
6.Ttemp;
7.if((ii8.elseif((jj9.else{temp=kk;}
10.returntemp;
11.}
12.voidmain()
13.{
14.cout<15.}
输出anderson与实际结果不否,原因在于编译器会生成对字符串指针做比较的函数,但比较字符串和比较字符串指针是不一样的,为了解决此问题,我们可以定制函数模板,如:
1.#include
2.#include
3.usingnamespacestd;
4.//定义函数模板,找出三个值中最小的值,与数据类型无关
5.template
6.Tmin(Tii,Tjj,Tkk)
7.{
8.Ttemp;
9.if((ii10.elseif((jj11.else{temp=kk;}
12.returntemp;
13.}
14.//非模板函数重载
15.constchar*min(constchar*ch1,constchar*ch2,constchar*ch3)
16.{
17.constchar*temp;
18.intresult1=strcmp(ch1,ch2);
19.intresult2=strcmp(ch1,ch3);
20.intresult3=strcmp(ch2,ch1);
21.intresult4=strcmp(ch2,ch3);
22.if((result1<0)&&(result2<0)){temp=ch1;}
23.elseif((result3<0)&&(result4<0)){temp=ch2;}
24.else{temp=ch3;}
25.returntemp;
26.}
27.voidmain()
28.{
29.cout<30.cout<31.cout<32.cout<33.}
在VS2010中,最后一行会输出Smith,与结果先符。
注意:
若上例在VC++6.0中运行,其结果最后一行仍会输出anderson,读者可自己上机查看情况并分析原因。
下面给出一些实例:
1.#ifndefHEADER_MY
2.#defineHEADER_MY
3.#include
4.#include
5.template
6.TfromString(conststd:
:
string&s)
7.{
8.std:
:
istringstreamis(s);
9.Tt;
10.is>>t;
11.returnt;
12.}
13.template
14.std:
:
stringtoString(constT&s)
15.{
16.std:
:
ostringstreamt;
17.t<
18.returnt.str();
19.}
20.
21.#endif
22.
23.
24.#include"HEADER.h"
25.#include
26.#include
27.usingnamespacestd;
28.intmain()
29.{
30.inti=1234;
31.cout<<"i==\""<32.floatx=567.89;
33.cout<<"x==\""<34.complexc(1.0,2.0);
35.cout<<"c==\""<36.cout<37.i=fromString(string("1234"));
38.cout<<"i=="<
39.x=fromString(string("567.89"));
40.cout<<"x=="<41.c=fromString>(string("(1.0,2.0)"));
42.cout<<"c=="<43.return0;
44.}
模板实参推演
当函数模板被调用时,对函数实参类型的检查决定了模板实参的类型和值的这个过程叫做模板实参推演。
如templatevoidh(Ta){};h
(1);h(0.2);第一个调用因为实参是int型的,所以模板形参T被推演为int型,第二个T的类型则为double。
在使用函数模板时,请注意以下几点:
在模板被实例化后,就会生成一个新的实例,这个新生成的实例不存在类型转换。
比如有函数模板templatevoidH(Ta){};inta=2;shortb=3;第一个调用H(a)生成一个int型的实例版本,但是当调用h(b)的时候不会使用上次生成的int实例把short转换为int,而是会另外生成一个新的short型的实例。
在模板实参推演的过程中有时类型并不会完全匹配,这时编译器允许以下几种实参到模板形参的转换,这些转换不会生成新的实例。
数组到指针的转换或函数到指针的转换:
比如templatevoidh(T*a){},intb[3]={1,2,3};h(b);这时数组b和类型T*不是完全匹配,但允许从数组到指针的转换因此数组b被转换成int*,而类型形参T被转换成int,也就是说函数体中的T被替换成int。
限制修饰符转换:
即把const或volatile限定符加到指针上。
比如templatevoidh(constT*a){},intb=3;h(&b);虽然实参&b与形参constT*不完全匹配,但因为允许限制修饰符的转换,结果就把&b转换成constint*。
而类形型参T被转换成int。
如果模板形参是非const类型,则无论实参是const类型还是非const类型调用都不会产生新的实例。
到一个基类的转换(该基类根据一个类模板实例化而来):
比如tessmplateclassA{};templateclassB:
publicA{};templatevoidh(A&m){},在main函数中有Bn;h(n);函数调用的子类对象n与函数的形参A不完全匹配,但允许到一个基类的转换。
在这里转换的顺序为,首先把子类对象n转换为基类对象A,然后再用A去匹配函数的形参A&,所以最后T2被转换为int,也就是说函数体中的T将被替换为int。
对于函数模板而言不存在h(int,int)这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行h(2,3)这样的调用,或者inta,b;h(a,b)。
模板实参推演实例,说明内容较长,采用注释形式,但代码较乱:
1.#include
2.usingnamespacestd;
3.templatevoidh(Ta){cout<<"h()"<4.templatevoidk(Ta,Tb){Tc;cout<<"k()"<模板类型形参T可以用来声明变量,作为函数的反回类型,函数形参等凡是类类型能使用的地方。
5.templatevoidf(T1a,T2b){cout<<"f()"<voidg(constT*a){Tb;cout<<"g()"<6.//templatevoidg(){}//错误,默认模板类型形参不能用于函数模板,只能用于类模板上。
7.//main函数开始
8.intmain()
9.{//templatevoidh(){}//错误,模板的声明或定义只能在全局,命名空间或类范围内进行。
即不能在局部范围,函数内进行。
10.//函数模板实参推演示例。
11.//h(int);//错误,对于函数模板而言不存在h(int,int)这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行h(2,3)这样的调用,或者inta,b;h(a,b)。
12.//h函数形式为:
templatevoidh(Ta)
13.h
(2);//输出"h()int"使用函数模板推演,在这里数值2为int型,所以把类型形参T推演为int型。
14.h(2.0);//输出"h()double",因为2.0为double型,所以将函数模板的类型形参推演为double型
15.//k函数形式为:
templatevoidk(Ta,Tb)
16.k(2,3);//输出"k()int"
17.//k(2,3.0);错误,模板形参T的类型不明确,因为k()函数第一个参数类型为int,第二个为double型,两个形参类型不一致。
18.//f函数的形式为:
templatevoidf(T1a,T2b)
19.f(3,4.0);//输出"f()int,double",这里不存在模板形参推演错误的问题,因为模板函数有两个类型形参T1和T2。
在这里将T1推演为int,将T2推演为double。
20.inta=3;doubleb=4;
21.f(a,b);//输出同上,这里用变量名实现推板实参的推演。
22.//模板函数推演允许的转换示例,g函数的形式为templatevoidg(constT*a)
23.inta1[2]={1,2};g(a1);//输出"g()int",数组的地址和形参constT*不完全匹配,所以将a1的地址T&转换为constT*,而a1是int型的,所以最后T推演为int。
24.g(&b);//输出"g()double",这里和上面的一样,只是把类型T转换为double型。
25.h(&b);//输出"h()double*"这里把模参类型T推演为double*类型。
26.return0;
27.}
函数模板的显示实例化
隐式实例化:
比如有模板函数templatevoidh(Ta){}。
h
(2)这时h函数的调用就是隐式实例化,既参数T的类型是隐式确定的。
函数模板显示实例化声明:
其语法是:
template函数反回类型函数名<实例化的类型>(函数形参表);注意这是声明语句,要以分号结束。
例如:
templatevoidh(inta);这样就创建了一个h函数的int实例。
再如有模板函数templateTh(Ta){},注意这里h函数的反回类型为T,显示实例化的方法为templateinth(inta);把h模板函数实例化为int型。
对于给定的函数模板实例,显示实例化声明在一个文件中只能出现一次。
在显示实例化声明所在的文件中,函数模板的定义必须给出,如果定义不可见,就会发生错误。
注意:
不能在局部范围类显示实例化模板,实例化模板应放在全局范围内,即不能在main函数等局部范围中实例化模板。
因为模板的声明或定义不能在局部范围或函数内进行。
显示模板实参:
1、显示模板实参:
适用于函数模板,即在调用函数时显示指定要调用的时参的类型。
2、格式:
显示模板实参的格式为在调用模板函数的时候在函数名后用<>尖括号括住要显示表示的类型,比如有模板函数templatevoidh(Ta,Tb){}。
则h(2,3.2)就把模板形参T显示实例化为double类型。
3、显示模板实参用于同一个模板形参的类型不一致的情况。
比如templatevoidh(Ta,Tb){},则h(2,3.2)的调用会出错,因为两个实参类型不一致,第一个为int型,第二个为double型。
而用h(2,3.2)就是正确的,虽然两个模板形参的类型不一致但这里把模板形参显示实例化为double类型,这样的话就允许进行标准的隐式类型转换,即这里把第一个int参数转换为double类型的参数。
4、显示模板实参用法二:
用于函数模板的反回类型中。
例如有模板函数templateT1h(T2a,T3b){},则语句inta=h(2,3)或h(2,4)就会出现模板形参T1无法推导的情况。
而语句inth(2,3)也会出错。
用显示模板实参就参轻松解决这个问题,比如h(2,3)即把模板形参T1实例化为int型,T2和T3也实例化为int型。
5、显示模板实参用法三:
应用于模板函数的参数中没有出现模板形参的情况。
比如templatevoidh(){}如果在main函数中直接调用h函数如h()就会出现无法推演类型形参T的类型的错误,这时用显示模板实