函数模板是通用的函数描述,即使用泛型来定义函数,其中的泛型可用具体的数据类型(如int,doubel等)替换。
由于模板允许以泛型的方式编写程序,所以有时也叫做通用编程。
由于类型是用参数表示的,因此模板特性有时也叫做参数化类型(parameterized types)。
template
void Swap(AnyType &a, AnyType &b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}
上述代码是一个交换两个变量值的模板函数;第一行:AnyType是定义的一个类型,这个类型名可以任意选择,但也要符合c++命名规则;尖括号是必须的;另外两个是关键字,跟着规则学就行,其中typename关键字可以用class替换,这是由于在标准c++98后才添加了typename关键字;
如果需要多个将同一种算法用于不同类型的函数,使用模板;
如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数时,使用关键字typename,不使用class;
下面程序就是创建了一个交换数值的模板函数,第一次传递的是int类型的,第二次传递的是double类型的;
说明一下,在调用模板函数时,编译器会自动生成对应的版本,实际上它只是使程序看起来更简单了,并没有缩短程序。
code_c++/funtemp.cpp · Kite/C和C++ - 码云 - 开源中国 (gitee.com)
需要对多个不同类型使用同一种算法的函数时,可使用模板;
但同样可以像重载常规函数定义那样重载模板定义;
与常规重载一样,被重载的模板的函数特征标必须不同;
还需注意一点,并非所有的模板参数都必须是模板参数类型。
假设有如下模板函数;
template
void f(T a, T b)
{
……;
}
a = b;//(1)
if(a > b);//(2)
T c = a * b;//(3)
假设有(1)的赋值语句,但如果T是数组,这就是错误的,这是因为数组初始化之后不能直接赋值;
假设有(2)的带有比较 > 的语句,但如果T是结构呢,这也是错误的;如果T是数组呢?由于数组名是地址,所以它比较的是数组的地址,我觉得这应该不是我们所需要的吧;
假设有(3)的带有乘法运算符 * 的语句,如果T是数组、指针或结构呢?这也是错误的。
假如结构中包含位置坐标,那么相加是有意义的,但是没有给结构定义运算符+,怎么解决呢?第一,允许重载运算符+,以便能够将其用于特定的结构或类;另一种,为特定类型提供具体化的模板定义。见下一小节介绍。
假设定义了如下结构;
struct job
{
char name[10];
double salary;
int floor;
};
假如要交换两个上述这种结构的内容,c++是允许将一个结构赋值给另一个结构的,但是如果只想交换结构中的部分内容,那么模板就不可以使用了。
但是,可以提供一个具体化函数定义----称为显式具体化(explicit specialization)。当编译器找到与函数调用相匹配的具体化定义时,将使用该定义,而不再寻找模板。
1.第三代具体化(ISO/ANSI C++标准)
c++98标准;
第一,对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本;
第二,显式具体化的原型和定义应以template<>打头,并通过名称来指出类型;
第三,具体化优先于常规模板,而非模板函数优先于具体化和常规模板;
下面是用于交换job结构的非模板函数、模板函数和具体化的原型;
void Swap(job &, job &);//非模板函数
template
void Swap(T &, T &);//模板函数
template <> void Swap(job &, job &);//具体化1
template <> void Swap(job &, job &);//具体化2
这里具体化1和具体化2都是可以的。
2.显式具体化示例
程序清单8.13;显式具体化; · 47a186d · Kite/C和C++ - Gitee.com
运行结果如下图;
代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。
编译器使用模板为特定类型生成函数模板时,得到的是模板实例。例如上面5小节的8.13程序,函数调用了Swap(i,j)导致编译器生成Swap()的一个实例,使用int类型。
模板不是函数定义,但是如上述所说的根据模板生成了一个int类型的实例,这个实例就是函数定义了。这种实例化方式被称为隐式实例化。
最初,编译器只能通过隐式实例化,来使用模板生成函数定义。
现在,c++还允许显式实例化,即通过直接命令编译器创建特定的实例。如Swap
template void Swap(int, int);
下面说一下自己对隐式实例化和显式实例化的区别:隐式是在调用函数时直接传递的参数是有类型的 ;而显示是提前说明了参数的类型。
上面也说了,显式具体化那一节有说明:有两种声明方式,是等价的,有了显式具体化的定义,那么每一个具体化函数都应该有自己的原型且有函数定义;显式具体化在关键字temolate后包含<>,显式实例化没有。
试图在一个文件(或转换单元)中使用同一类型的显式实例和显式具体化将出错。
还可通过在程序中使用函数来创建显式实例化。如下代码;
template
T Add
{
return a + b;
}
……
int m = 6;
double x = 10.2;
cout << Add(x, m) << endl;
这里模板和函数调用是不匹配的,因为在模板要求的参数类型是相同的;但通过使用 Add
隐式实例化、显式实例化和显式具体化统称为具体化。它们有一个共同点:表示的都是使用具体类型的函数定义,而不是通用描述。
最后不要搞混了实例化和显式具体化,以及函数模板的区别。
看到这里,我们学了函数重载、函数模板和函数模板重载,大家都是一脸懵,那么对于c++如何去决定为函数调用使用哪一个函数定义呢?尤其在有多个参数时。很显然是有的,这个过程就叫做重载解析。
下面就了解一下这个过程是如何进行的;
第一步:创建候选函数列表。其中包含与被调用函数的名称相同的函数和函数模板。
第二步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,
其中包括实参类型与相应的形参类型完全匹配的情况。
第三步:确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错。
下面举一个例子;
例如:may(‘B’);
首先,编译器将寻找候选人,即名称为may()的函数和函数模板。然后再看可以用一个参数调用的函数。
例如有下述这几个函数符合要求;
void may(int);
float may(float, float = 3);
void may(char);
char * may(const char *);//4
char may(const char &);
template void may(cosnt T &);
template void may(T *);//7
首先4和7不行,参数是指针类型的,但是这里整数类型不能被隐式的转换为指针类型。
然后就要找出剩余5个中最佳的选择了。
编译器会查看函数调用参数与可行的候选函数的参数匹配所需要进行的转换。
通常来说,从最佳到最差的顺序如下;
1,完全匹配,但常规函数优先于模板;
2,提升转换,例如char或short转换为int,float转换为double;
3,标准转换,例如int转换为char,long转换为double;
4,用户定义的转换,如类声明中定义的转换;
例如上面举得例子,1是提升转换,2是标准转换,3,5,6都是完全匹配;
但是完全匹配中6是模板,所以现在感觉最优的是3和5;
但是前面学习也学过,多个最优选择会被认为是错误的,那么这里是怎么回事呢?
下面就要介绍一下完全匹配和最佳匹配了。
进行完全匹配时,某些无关紧要的转换是允许的。Type表示任意类型。
Type意味着用作实参的函数名字与用作形参的函数指针只要返回类型和参数列表相同就是完全匹配。
下表是完全匹配允许的无关紧要转换
下面举个简单例子说明问题;
有下面的函数;
struct blot {int a; char b[10];};
blot ink = {25, "sports"};
那么下面的原型都是完全匹配的;
void rec(blot);//1
void rec(const blot);//2
void rec(blot &);//3
void rec(const blot &);//4
有多个匹配选项,编译器无法完成重载解析过程;如果没有最佳选择,编译器就会报错,生成一条错误消息,使用诸如“ambiguous(二义性)”这样的词语。
但是有些时候,即使两个函数都完全匹配,仍可完成重载解析。
例如,rec()示例,如果只定义了3和4完全匹配,就选择3了,因为ink未被声明为const。也就是说,指向非const数据的指针和引用优先与非const指针和引用参数匹配。但是注意了,const和非const这个区别只是适用于指针和引用指向的数据。例如,如果只定义了1和2就会出现二义性错误。
非模板函数优先于具体化和常规模板,第5小节也强调过。
那么如果都是模板函数呢?显式具体化优先于使用模板隐式生成的具体化。
“最具体”不一定就是显式具体化,而是指编译器推断使用哪种类型时执行的转换最少;例如;
template void rec (Type T);//模板1
template void rec (Type * T);//模板2
struct blot {int a; char b[10];};
blot ink = {25, "sports"};
rec(&ink);
现在说明一下模板1,Type --> blot*;隐式实例rec
模板2,Type --> ink,也就是blot;隐式实例rec
用于找出最具体的模板的规则被称为函数模板的部分排序规则。