(五)函数模板
1.函数模板的使用:属于泛型编程的一种
函数模板,template <typename AnyType>,指出:建立一个模板,并将类型命名为AnyType。关键字template和 typename 是必须要有的,除非可以用class来代替typename。可以使用更简单一点的声明,template <class T> function(){}。声明了函数模板之后,我们可以使用T来标识所有的数据类型,这样,当我们调用函数的时候,编译器会根据数据的类型,自动生成相匹配的函数,从而可以简化编程。简而言之,就是需要对多种数据类型使用同一种算法的时候可以使用函数模板。
函数模板使用的时候,首先要声明template,后面是尖括号,比如template <class T>或者template <typename xx>;然后后面不加任何东西,直接加函数的声明,并且在函数的声明中使用T或xx来作为类型的名称。函数定义部分的书写和声明的书写是一样的,也就是前面的template是不可以省略的。
函数模板在调用的时候,会根据参数的类型自动生成相应的函数定义,从而简化编程。当需要将同一算法应用于不同类型的时候,请使用函数模板;使用函数模板的时候,如果不需要向前兼容,那么尽量使用关键字typename,而不是使用class。
使用函数模板的时候,可以像使用常规函数一样,编译器会自动根据所给定的值的类型来生成相应的类型的函数,而不需要像容器那样手动指定类型。也就是说在函数实际运行的时候,并没有函数模板,只有按照函数模板根据不同的类型生成的具体的函数。
2.函数模板的重载
函数模板重载和函数重载一样,需要有不同的函数特征标,比如引用和指针,或者多个参数,函数模板中,并不是每一个参数都要是模板类型,可以是普通的类型int等。
3.模板的局限性
编写的模板很可能无法处理某些类型,比如,a=b;语句,如果a与b是数组名,便不能成立。再比如a>b表达式,如果a与b是结构,便也不能成立。解决方法有两种,一种是使用运算符重载,另一种更有用的是为特定的类型提供具体化的模板定义。
4.显式模板具体化
对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数三种类型以及他们的重载版本;具体化优先于常规模板,而非模板又优先于具体化;
显式具体化的原型和定义应该使用template <>来打头,并通过名称来指出类型。应用举例:显示具体化模板函数声明:template <> void swap<job>(job &,job &);其中swap后面的尖括号中的job类型是可选的,也就是可以用template<> void swap(job &,job &);来代替上述声明。显式具体化模板的template后面紧接着的尖括号中是空的,这是与普通模板的区别。
5.实例化和具体化
!!!!!在代码中包含函数模板并不会生成函数定义,它只是一个用于生成函数定义的方案。
通过模板生成函数定义就叫作模板的实例化,生成函数定义是编译器的工作,模板不能生成函数定义,但是使用int的模板实例会生成函数的定义。这种实例化的方式被称为隐式实例化。
还有一种实例化是显示实例化,意味着可以直接命令编译器来创建特定的实例。显示实例化的意思是“使用模板生成某种特定类型的函数定义”用法如下:template void swap <job> (job &,job &);显式具体化的意思是:“不要使用模板生成函数定义,要使用这个具体的类型生成函数定义”。显式实例化和显式具体化的区别是显式实例化后面没有<>,其他都相同。显式实例化的本质是指定特定的类型为模板生成函数定义,使用的还是原来那个模板函数;而显式具体化的本质是生成特定类型的函数定义的方案并将它实例化,也就是说使用的模板不再是原来的函数模板,而是为特定的类型专门制定的模板。
还有一种实例化的方式是在程序中创建函数来实现实例化(这也是一种显式实例化),但是这时候参数可能不匹配,因此,需要声明参数的类型,这种方法相当于显式实例化,但是没有使用template关键字。方法如下,比如template <class T> T show(T,T);int x=2;double y=3.22;cout<<show<int>(x,y);这时候参数是两种类型,与模板中要求两个参数是同一种类型不符合,但是我们可以显式地使用模板实例化,来让两个参数都是int类型,这样调用函数的时候double类型的y会被强制类型转换为int。
显式实例化,隐式实例化,显式具体化被统称为具体化,它们都是对具体类型的函数的定义,而不是函数的通用描述,显式实例化和显式具体化的区别是显示实例化template后面没有尖括号。
6.编译器选择使用哪个函数版本——重载解析
(1)也就是同名函数选择的优先级的问题:完全匹配,但常规函数优于模板;提升转换;标准转换;用户定义的转换。但完全匹配有时候也有优先级,一般来说越具体,优先级越高(比如int &就比const int &优先级要高,指针也是如此,常量引用或指针与具体的类型相比,优先级要低)。
(2)如果有多个参数,那么选择的复杂程度更高,我们不去深究,这些规则主要是为了产生确定的结果,而不会有歧义。当我们用到这些内容的时候,要注意使表达越清晰越好,而不是功能越多越好。
(3)找出最具体的模板的规则叫作函数模板的部分排序规则(partial ordering rules),什么是越具体呢?说白了就是执行的转换越少越具体,比如T可以表示一个基本类型也可以表示一个指针或引用的时候,编译程序会将T表示为一个基本类型,因为它执行的转换更少,也就是更具体。
(4)自己选择。有时候我们需要自己选择出一个恰当的函数,来代替可能会不正确的按优先级自动选择的函数。比如我们定义了一个模板函数,而且还定义了有具体类型的函数,但此时我们依然希望使用模板函数,要如何操作呢?此时可以用如下的方法:比如lesser是一个模板函数,我可以使用lesser<>(a,b)来使用模板函数的隐式实例化,也可以使用lesser<int>(a,b)来使用模板的显式实例化。
7.模板函数的发展
(1)模板函数使用过程中出现的问题:
第一是并不是总能知道应该声明的类型,比如T,M都是模板类型,则T b;M c;type a=b+c;则a应该使用什么类型呢?我们无法预先做出判断。第二种出现的情况是函数的返回值的类型,由于在函数调用之前,我们不知道函数将使用什么类型的参数,因此函数返回值的类型我们也无法得知,不能来声明。为了解决这两个问题,c++98和c++11分别提出了两个方案,一个是decltype关键字来声明返回类型,另一个是用函数后置返回类型的方法。
(2)关键字decltype(declare type的缩写)
用法举例:int a;decltype(a) b;则声明了一个和a类型一样的变量b。再比如int x,double y;decltype(x+y) z;则声明了一个和x+y类型相同的变量z。
采用这种方式就可以在模板函数中声明预先不知道类型的变量了,比如有两个模板类型T和M,可以使用decltype(T+M)xx;来声明一个类型是T+M的变量xx。
Decltype关键字确定类型的流程:假设有一个decltype(expression)var;声明。则第一步,如果expression是一个不带括号的标识符,则var的类型与此标识符的类型相同,包括const等限定符。第二步,如果expression是一个函数调用,则var的类型与函数的返回值的类型相同(不调用函数,只是去查看原型)。第三步,如果expression是一个用括号括起来的左值,则var的类型是这个左值的引用,比如int a;decltype((a))b;则b是int &类型。第四步,如果前面的都不满足,则var的类型与expression的类型相同,比如expression是一个表达式的情况。
(3)另一种函数声明语法:函数后置返回类型
c++11新增加了另一种声明和定义函数的语法,也就是返回类型后置声明语法。用来处理不能确定函数返回类型的情况。
使用关键字auto,auto是一个占位符,并没有实际作用。再加上后面的->类型来声明函数的返回类型,这样可以和decltype配合,从而使模板函数可以适应不同的情况。
用法举例:template<class T1,class T2> auto gt(T1 x,T2 y)->decltype(x+y){...;return x+y;}此时x和y在参数声明的后面,因此x,y位于作用域内,可以使用它们。
网友评论