C++之所以强大,其中肯定少不了模板的功劳。使用好模板也可以为以后的编程省去很多的功夫。
函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中泛型可用具体的类型(如int或double) 替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型的方式编写程序,因此有时也被称为通用编程。由于类型使用参数表示的,因此模板特性有时也被成为参数化类型(parameterized types) (C++ Primer Plus 第六版中文 P281)
以上就是一个函数模板的定义。
首先如果没有函数模板, 试想如果需要一个函数来交换两个数字的值,这个时候由于不知道两个数字的类型,你需要使用函数重载来写很多个类型的函数,如
void Swap(int &, int &);
void Swap(double &, double &);
void Swap(char &, char &);
void Swap(short &, short &);
......
作为一个程序员怎么会容忍这么多相似度极高并且效率低下的事情发生呢? 于是模板就出现了。上面的类型只需要短短几行全部就可以搞定。
template <typename AnyType>
void Swap(AnyType &a, AnyType &b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}
其中typename这个关键字也可以使用class来代替。在标准C++98添加关键字typename之前,c++都是使用关键字class来创建模板。
这个模板可以把上面那么多行的函数全部都替换掉,是不是感觉就方便了许多。
但是万一我们想要交换两个数组怎么办,那之前这个模板就不管用了。别着急,c++的模板也支持重载
这意味着你可以像重载函数一样的重载你的模板,这样你的代码试用的范围就更加的广泛。
尽管模板很好用,但是还是有一些局限性。
template <class T>
int f(T a, T b)
{
return a>b;
}
在这个代码中,加入比较的是结构体那么就不成立,如果是数组那可能结果就和你所预期的不一样。
为了解决这些问题,c++有两种解决方法。
1.c++允许重载运算符,这样的话就可以用于特定的结构体或者数组上
2.可以为特定类型提供具体化的模板定义。
那么这次我们就来讨论第二种解决方法。
假设定义了如下结构:
struct job
{
char name[40];
double salary;
int floor;
}
假设我们想要使用上面的Swap模板来交换这个结构体。c++允许将一个结构体赋值给另一个结构体,因此这个Swap模板是可以使用,但是假如我们只想交换salary和floor成员,而不交换name成员怎么办,那么我们需要使用不同的代码。
然而c++第二个解决方法就是提供一个具体化函数定义----成为显示具体化(explicit specialization), 其中包含所需代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
template <typename T>
void Swap(T &, T &);
//这个是常规模板
template <> void Swap<int> (int &, int &);
//这个是具体化模板
1.对于给定的函数名,可以有非模板函数、模板函数、和显式具体化模板函数以及它们的重载版本
2.具体化模板是由于常规模板,而非模板函数由于具体化和常规模板。
3.显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
当然这个模板原型也可以这样写
template <> void Swap(int &, int&);
实例化和具体化
为了进一步了解模板,必须理解术语实例化和具体化。记住,在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(iinstantiation)。 例如函数调用Swap(i,j)导致编译器生成Swap()的一个实例,该实例使用int类型。模板并非函数定义,但是用int的模板实例是函数定义。这种实例化方式被称为隐式实例化(implicit instantiation)。
最初编译器只能通过隐式实例化,来使用模板生成的函数定义。但现在c++还允许显示实例化(explicit instantiation)。使用这种实例化的方式是:
template void Swap<int> (int &, int &);
实现这种特性的编译器看到上述声明后,会使用Swap()模板生成一个使用int类型的实例。也就是说,该声明的意思是“使用Swap()模板生成int类型的函数定义”
隐式实例化、显示实例化和显示具体化统称为具体化(specialization)。它们的相同之处在于,他们表示的都是使用具体类型的函数定义,而不是通用描述。
编译器选择使用哪个函数版本
对于函数重载、函数模板和函数模板重载,c++需要一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析(overloading resolution)。大概过程如下
1. 创建候选函数列表。其中包含于被调用函数的名称相同的函数和模板函数。
2. 使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数。
3. 确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错。
以上就是函数模板的一个大概初探,深入的后面还会写到,希望大家都可以利用好模板这个强大的工具。