1、定义:什么是函数模板?
一个可以应用于不同类型的对象的函数叫做函数模板。
函数模板可以让我们在不知道处理的数据是什么类型的情况下进行编程。
2、起因:为什么C++中要增加模板?
看下面三个函数:
void swap(int& v1, int& v2)
{
int temp;
temp = v1;
v1 = v2;
v2 = temp;
}
void swap(double& v1, double& v2)
{
double temp;
temp = v1;
v1 = v2;
v2 = temp;
}
void swap(string& v1, string& v2)
{
string temp;
temp = v1;
v1 = v2;
v2 = temp;
}
他们除了要处理的数据类型之外完全相同,我们自然就有这么一个想法:能否用同一个函数代替这三个?
也就是说,能不能写一次就完成这三倍量的代码?
模板的功能使这种想法成为可能。
3、格式:应该怎么写一个模板?
template < 模板形参表 >
返回值类型 函数名(形式参数列表)
{
函数体语句
}
注意:
1、模板的保留字是template,后面跟的模板形参表不能为空。
2、后面的函数形参列表中应该包含模板形参表中出现的所有形参。
3、系统内置类型(如int,double等)模板形参表格式如下:typename 模板形参1, typename 模板形参2, …
4、类类型的模板形参表格式如下:class 模板形参1, class 模板形参2, …
5、函数模板的使用形式与普通函数调用相同,但实际过程不同。
所以,如果采用模板,上面的三个函数可以用一个函数代替:
template <typename T>
void swap( T& v1, T& v2)
{
T temp;
temp = v1;
v1 = v2;
v2 = temp;
}
或者:
template <class T>
void swap( T& v1, T& v2)
{
T temp;
temp = v1;
v1 = v2;
v2 = temp;
}
4、进一步:编译器都帮我们做了什么?
调用函数模板的实际过程:
1. 模板实参推断( template argument deduction):编译器根据函数调用中所给出的实参的类型,确定相应的模板实参。
- 函数模板的实例化( instantiation):模板实参确定之后,编译器就使用模板实参代替相应的模板形参,产生并编译函数模板的一个特定版本(称为函数模板的一个实例( instance))(注意:此过程中不进行常规隐式类型转换)。
5、重载:如何确定调用哪个函数?
对函数模板进行重载:
定义名字相同而函数形参表不同的函数模板,或者定义与函数模板同名的非模板函数,在其函数体中完成不同的行为。
函数调用的静态绑定规则
如果某一同名非模板函数的形参类型正好与函数调用的实参类型匹配(完全一致),则调用该函数。否则,进入第2步。
如果能从同名的函数模板实例化一个函数实例,而该函数实例的形参类型正好与函数调用的实参类型匹配(完全一致),则调用该函数实例。否则,进入第3步。
对函数调用的实参作隐式类型转换后与非模板函数再次进行匹配,若能找到匹配的函数则调用该函数。否则,进入第4步。
提示编译错误。
补充:显示具体化——另一种模板形式
假设定义了以下结构:
struct job {
char name[40];
double salary;
int floor;
};
假设只想交换其中的salary和floor成员,将无法使用常规的两个变量交换的模板。这里可以用到显示具体化。
首先说说C++98的相关标准:
1、对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本。
2、显示具体化的原型和定义应以template<>打头,并通过名称来指出类型。
3、显示具体化优于常规模板,而非模板函数优于具体化和常规模板。
下面是用于交换job的显示具体化:
template <> void Swap<job>(job &, job &);
其中,Swap中的是可选的。因为函数的参数已经表明,这是job的一个具体化。
注意
要区分显示具体化和显示实例化:
template <> void Swap<job>(job &, job &); //显示具体化
template void Swap<char>(char &, char &); //显示实例化
如上,两者是通过template <> 和template 来区分的。
通常,在同一个文件中,同时对某一类型提供显示具体化和显示实例化将出错。
声明:以上整理自个人理解和中山大学万海讲师上课内容以及《C++ Primer Plus》。