函数模板是指这样的一类函数:可以用多种不同数据类型的参数进行调用,代表了一个函数家族。它的外表和普通的函数很相似,唯一的区别就是:函数中的有些元素是未确定的,这些元素将在使用的时候才被实例化。先来看一个简单的例子:
下面的这个例子就定义了一个模板函数,它会返回两个参数中最大的那一个:
这个函数模板定义了一个“返回两个值中最大者”的函数家族,而参数的类型还没有确定,用类型模板参数T来确定。模板参数需要使用如下的方式来声明:
template< 模板参数列表 >
在这个例子中,模板参数列表为:typename T。关键字typename引入了T这个类型模板参数。当然了,可以使用任何标识符作为类型模板参数的名称。我们可以使用任何类型(基本数据类型、类类型)来实例化该函数模板,只要所使用的数据类型提供了函数模板中所需要的操作即可。例如,在这个例子中,类型T需要支持operator <,因为a和b就是通过这个操作符来比较大小的。
鉴于历史原因,也可以使用关键字class来取代typename来定义类型模板参数,然而应该尽可能地使用typename。
下面的程序使用了上面定义的这个函数模板:
通常而言,并不是把模板编译成一个可以处理任何类型的单一实体,而是针对于实例化函数模板参数的每种类型,都从函数模板中产生出一个独立的函数实体。因此,针对于每种类型,模板代码都被编译了一次。这种用具体类型代替模板参数的过程,叫做模板的实例化。它产生了一个新的函数实例(与面向对象程序设计中的实例化不同)。
如果试图基于一个不支持模板内部所使用的操作的类型实例化一个模板,那么将会引发一个编译期错误:
所以说:模板被编译了两次,分别发生于:
模板实例化之前,查看语法是否正确,此时可能会发现遗漏的分号等。
模板实例化期间,检查模板代码, 查看是否所有的调用都有效。此时可能会发现无效的调用,例如实例化类型不支持某些函数调用等。
所以这引发了一个重要的问题:当使用函数模板并且引发模板实例化时,编译器必须查看模板的定义。事实上,这就不同于普通的函数,因为对于普通的函数而言,只要有函数的声明(甚至不需要定义),就可以顺利地通过编译期。
当我们为某些实参调用一个函数模板时,模板参数可以由我们所传递的实参来决定。
注意:函数模板在推断参数类型时,不允许自动类型转换,每个类型模板参数都必须正确的匹配。
注意:模板实参推断并不适合返回类型。因为返回类型并不会出现在函数调用参数的类型里面。
所以,必须要显示地指定返回类型:
和普通的函数一样,函数模板也可以被重载。在下面的例子中,一个非模板函数可以和一个同名的函数模板同时存在,这称为函数模板的特化。而且该函数模板还被实例化为这个非模板函数。
总结如下:
(1)对于非模板函数和同名的函数模板,如果其它条件都是相同的话,那么在调用的时候,重载解析过程中会优先调用非模板函数,而不会实例化模板(04)。
(2)如果模板可以产生一个具有更好匹配的函数,那么将选择模板(02, 03)。
(3)还可以显示地指定一个空的模板参数列表,告诉编译器:必须使用模板来匹配(05)。
(4)由于函数模板拒绝隐式类型转换,所以当所有的模板都无法匹配,但是发现可以通过强制类型转换来匹配一个非模板函数时,将调用那个函数(07)。
在重载函数模板时,请谨记:将对函数声明的改变限制在以下两种情况中:
改变参数的数目
显示指定模板的参数(即函数模板特化)
否则,很可能会导致非预期的结果,例如在下面的例子中,模板函数是使用引用进行传参的,然而在其中的一个重载中(实际上是针对char*进行的特化),却使用了值传递的方式,这将会导致不可预期的结果:
学习C/C++编程知识,想要成为一个更加优秀的程序员,或者你学习C/C++的时候有难度,可以来博主的C语言C++零基础编程学习圈,里面不仅有学习视频和文件资料,还有更多志同道合的朋友,欢迎转行也学习编程的伙伴,和大家一起交流成长会比自己琢磨更快哦!
微信公众号:C语言编程学习基地