
一、什么是模板
一言以蔽之——模板就是用来偷懒的,如果一个函数的功能可以用在不同数据类型的参数上,那么就可以使用模板。
在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推断数据类型。这就是类型的参数化。所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。
一个最简单的模板如下(想用指针也可以):
template
总结一下,模板函数的语法:
template
typename用class也可以,但是最好不要,以免和类混淆。使用该函数的时候不需特地声明参数数据类型,在自己写的模板允许的范围内即可。
除了函数模板,类也可以使用模板,比如说下面的例子:
template
总结一下,模板类的语法:
template
上面仅仅是类的声明,如果类外定义成员函数,每个都要写上template
template
总结一下:模板类的成员函数的语法:
template
那么,如何使用模板来创建对象呢?
Point
也许有人会问:模板除了数据类型之外,是否可以在其他方面重载?答案是肯定的:
#include
模板所支持的类型是宽泛的,没有限制的,我们可以使用任意类型来替换,这种编程方式称为泛型编程。
二、模板的实参推断
使用模板函数,虽然不像使用模板类那样需要显式声明数据类型,但是编译器会通过函数实参来确定模板实参,这个过程叫做模板实参推断。
P.S:通常情况下,普通函数(非模板函数),发生函数调用时会对实参的类型进行适当的转换,以适应形参的类型。这些转换包括:
- 算数转换:例如 int 转换为 float,char 转换为 int,double 转换为 int 等。
- 派生类向基类的转换:也就是向上转型。
- const 转换:也即将非 const 类型转换为 const 类型,例如将 char * 转换为 const char *。
- 数组或函数指针转换:如果函数形参不是引用类型,那么数组名会转换为数组指针,函数名也会转换为函数指针。
- 用户自定的类型转换。
但是对于模板函数,类型转换限制更多,仅能进行const转换和数组或函数指针转换。比如当函数形参是引用类型时,数组不会转换为指针。像下面这个例子:
template
如果这样调用它:
int
那么,编译器不知道将T实例化为int[20]还是int[10],导致调用失败。
程序员想自己掌握主动权的话,可以为函数模板显式指明实参,因为当类型参数的个数较多时,就会有个别的类型无法推断出来,这个时候就必须显式地指明实参。假设有这么个极端的例子:
template
func() 有两个类型参数,分别是 T1 和 T2,但是编译器只能从函数调用中推断出 T1 的类型来,不能推断出 T2 的类型来,所以这种调用是失败的,这个时候就必须显式地指明 T1、T2 的具体类型。
可以这么做:
func
显式指明的模板实参会按照从左到右的顺序与对应的模板参数匹配:第一个实参与第一个模板参数匹配,第二个实参与第二个模板参数匹配,以此类推。只有尾部(最右)的类型参数的实参可以省略,而且前提是它们可以从传递给函数的实参中推断出来。
对于上面的 func(),虽然只有 T2 的类型不能自动推断出来,但是由于它位于类型参数列表的尾部(最右),所以必须同时指明 T1 和 T2 的类型。对代码稍微做出修改:
template
由于 T2 的类型能够自动推断出来,并且它位于参数列表的尾部(最右),所以可以省略。
上面我们提到,函数模板仅能进行const 转换和数组或函数指针转换两种形式的类型转换,但是当我们显式地指明类型参数的实参(具体类型)时,就可以使用正常的类型转换(非模板函数可以使用的类型转换)了。
template
在第二种调用形式中,我们已经显式地指明了 T 的类型为 float,编译器不会再为「T 的类型到底是 int 还是 double」而纠结了,所以可以从容地使用正常的类型转换了。
三、模板的非类型参数
什么是非类型参数?非类型参数用来传递数据的值而不是类型,它和普通函数的形参一样,都需要知名具体的类型。类型参数和非类型参数都可以用在函数体或者类体中。当调用一个函数模板或者通过一个类模板创建对象时,非类型参数会被用户提供的、或者编译器推断出的值所取代。
前文我们写了数组交换的函数,为什么这个函数不能直接通过使用sizeof运算符来求数组的长度?因为数组作为函数参数传参时会自动转换为数组指针,但是sizeof只能通过数组名求得数组长度,不能通过数组指针求得数组长度。通过使用非类型参数可以把参数列表里面的len去掉:
template
T (&a)[N]表明a是一个引用,其引用的数据类型是T[N],也即一个数组。
关于指针的解析,这篇说的很好:只需一招,彻底攻克C语言指针,再复杂的指针都不怕c.biancheng.net
那么我们用非类型参数来重写上面的Swap例子:
#include
模板类中也可以使用非类型参数
#include
然而非类型参数也有限制。首先非类型参数的类型不能随意指定,它受到了严格的限制,只能是一个整数,或者是一个指向对象或函数的指针或引用。
- 当非类型参数是一个整数时,传递给它的实参,或者由编译器推导出的实参必须是一个常量表达式。举两个例子了解一下:
int
在这两个例子中,传递给非类型参数的不是具体数值,而是变量,故报错。
2. 当非类型参数是一个指针(引用)时,绑定到该指针的实参必须具有静态的生存期;换句话说,实参必须存储在虚拟地址空间中的静态数据区。局部变量位于栈区,动态创建的对象位于堆区,它们都不能用作实参。也就是说,指针或引用,必须是常量(const)。
(如有转载请注明作者与出处,欢迎建议和讨论,thanks)