函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。函数模板的声明形式为:
template<typename 数据类型参数标识符>
<返回类型><函数名>(参数表)
{
函数体
}
举个例子:
template <typename T>
void Swap(T &a,T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
其中,template是定义模板函数的关键字,T为任意类型,只是为了方便取的一个名称;template后面的尖括号不能省略;typename(或class)是声明数据类型参数标识符的关键字,用以说明它后面的标识符是数据类型标识符。这样,在以后定义的这个函数中,凡希望根据实参数据类型来确定数据类型的变量,都可以用数据类型参数标识符来说明,从而使这个变量可以适应不同的数据类型。在c++98添加关键字typename之前,c++都是使用关键字class来创建模板的。
示例代码
template <class T>
void Swap(T &a,T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
示例代码
#include <iostream>
#include <stdlib.h>
template <class T>//或者是class T
void Swap(T &a, T &b);//Swap首字母必须大写,不然会报错,因为std里面有一个swap交换函数,会冲突
int main()
{
using namespace std;
int i = 10;
int j = 20;
cout << "i, j = "<<i<<", "<<j<<".\n";
cout << "using compiler-generated int swapper:\n";
Swap(i,j);
cout << "now i, j = "<<i<<", "<<j<<".\n";
double x = 24.5;
double y = 81.7;
cout << "x, y = "<<x<<", "<<y<<".\n";
cout << "using compiler-generated double swapper:\n";
Swap(x, y);
cout << "now x, y = " << x << ", " << y << ".\n";
system("pause");
return 0;
}
template <class T>
void Swap(T &a,T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
演示效果
重载的模板
之前学过,常规函数重载的作用是通过重载同一个函数名处理相似的工作,举个例子:1+2+3是int类型相加,1.0+2.0+3.0是double类型的数相加,如果用一个相同函数名Add就非常方便,就是参数列表不能相同,而模板是用来处理类型不同的数据的,也就是参数类型不同,即所有类型通用,看着感觉有点相似,都是参数在变化,如果函数重载+函数模板会发生什么情况呢?这样就把这两种情况的特性结合在一起了,有了模板所有类型就可通用,只要参数数目对齐,有了重载就可以用同一个函数名去实现相似的重复工作,带来方便。
示例代码
#include <iostream>
#include <stdlib.h>
template <typename T>//或者是class T
void Swap(T &a, T &b);//Swap首字母必须大写,不然会报错,因为std里面有一个swap交换函数,会冲突
template <typename T>
void Swap(T a[],T b[],int n);
void Show(int a[]);
const int Lim = 8;
int main()
{
using namespace std;
int i = 10, j = 20;
cout << "i, j = "<<i<<", "<<j<<endl;
cout << "using compiler-generated int swapper:\n";
Swap(i,j);
cout << "Now i, j = " << i << ", " << j << endl;
int d1[Lim] = {0,7,0,4,1,7,7,6};
int d2[Lim] = {0,7,2,0,1,9,6,9};
cout << "Original arrays:\n";
Show(d1);
Show(d2);
Swap(d1,d2,Lim);
cout << "Swapped arrays:\n";
Show(d1);
Show(d2);
system("pause");
return 0;
}
//交换两个数字
template <typename T>
void Swap(T &a,T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
//交换两个数组
template <typename T>
void Swap(T a[],T b[],int n)
{
T temp;
for (int i=0;i<n;i++)
{
temp=a[i];
a[i] = b[i];
b[i] = temp;
}
}
void Show(int a[])
{
using namespace std;
cout << a[0] << a[1] << "/";
cout << a[2] << a[3] << "/";
//一共8个数,依次输出后4个数字
for (int i=4;i<Lim;i++)
{
cout << a[i];
}
cout << endl;
}
演示效果
显式具体化
假设定义了一个结构:
struct job
{
char name[40];
double salary;
int floor;
};
如果要交换结构的内容,可以这样做:
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
但这样做的话,就把结构里面所有的内容都进行了交换,下面会给出结果。如果我不想把所有的内容都交换,而只交换salary和floor,我们可以用显式具体化来做。
1、首先,我们来把函数分类:
根据同一个函数名我们可以分为:非模板函数(常规函数)、模板函数和显式具体化模板函数以及他们的重载函数。
2、接下来,我们来排一下多个函数原型的调用顺序(前提条件是实现的功能要一样,不然的话就根据函数定义来调用):
非模板函数(常规函数)>显式具体化模板函数>模板函数
3、下面给出三种类型的模板原型:
非模板函数(常规函数):
void Swap(job &a,job &b);
模板函数:
template <typename T>
void Swap(T &a,T &b);
显式具体化模板函数:
template <> void Swap<job>(job &a,job &b);
显式具体化模板函数的函数原型和定义必须以template <>开头,并通过名称来指定类型,但是<job>这里的job可以省略掉。
template <> void Swap<>(job &a,job &b);
这样也是可以的。
为了体现显式具体化模板函数的作用,我把代码做一下修改,得出不同的结果:
1、第一种 ,不用显式具体化
template <> void Swap<>(job &j1,job &j2);
template <> void Swap<>(job &j1,job &j2)
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}
示例代码
#include <iostream>
#include <stdlib.h>
template <typename T>
void Swap(T &a,T &b);
struct job
{
char name[40];
double salary;
int floor;
};
//template <> void Swap<>(job &j1,job &j2);
void show(job &j);
int main()
{
using namespace std;
cout.precision(2);
cout.setf(ios::fixed,ios::floatfield);
int i = 10;
int j = 20;
cout << "i, j = "<<i<<", "<<j<<".\n";
cout << "using compiler-generated int swapper:\n";
Swap(i,j);
cout << "now i,j = "<<i<<", "<<j<<".\n";
job sue = {"susan yaffee",73000.68,7};
job sidney = {"sidney taffee",78060.72,9};
cout << "before job swappcing:\n";
show(sue);
show(sidney);
Swap(sue,sidney);
cout << "after job swapping:\n";
show(sue);
show(sidney);
system("pause");
return 0;
}
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
/*template <> void Swap<>(job &j1,job &j2)
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}*/
void show(job &j)
{
using namespace std;
cout << j.name << ": $" << j.salary << " on floor "<<j.floor<< endl;
}
演示效果
注意结果中标记的部分,名字也交换了,也就是整个结构的内容都交换了
2、看下一组,用了显式具体化模板函数,我把标红的部分给打开了
示例代码
#include <iostream>
#include <stdlib.h>
template <typename T>
void Swap(T &a,T &b);
struct job
{
char name[40];
double salary;
int floor;
};
template <> void Swap<>(job &j1,job &j2);
void show(job &j);
int main()
{
using namespace std;
cout.precision(2);
cout.setf(ios::fixed,ios::floatfield);
int i = 10;
int j = 20;
cout << "i, j = "<<i<<", "<<j<<".\n";
cout << "using compiler-generated int swapper:\n";
Swap(i,j);
cout << "now i,j = "<<i<<", "<<j<<".\n";
job sue = {"susan yaffee",73000.68,7};
job sidney = {"sidney taffee",78060.72,9};
cout << "before job swappcing:\n";
show(sue);
show(sidney);
Swap(sue,sidney);
cout << "after job swapping:\n";
show(sue);
show(sidney);
system("pause");
return 0;
}
template <typename T>
void Swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
template <> void Swap<>(job &j1,job &j2)
{
double t1;
int t2;
t1 = j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
t2 = j1.floor;
j1.floor = j2.floor;
j2.floor = t2;
}
void show(job &j)
{
using namespace std;
cout << j.name << ": $" << j.salary << " on floor "<<j.floor<< endl;
}
void show(job &j)
{
using namespace std;
cout << j.name << ": $" << j.salary << " on floor "<<j.floor<< endl;
}
演示效果
注意标记的地方名字是没有改变的,只改了后面两项,所以这就是具体化模板函数的优势所在
编译器选择哪个函数版本
重载解析:当程序中出现了多个函数重载,函数模板和函数模板重载时,函数调用将选择哪一个正确的函数定义。
假如只有一个参数的情况下的函数调用:
may('B');
下面是对应的函数和函数模板:
1、void may(int);
2、float may(float);
3、void may(char);
4、char *may(const char *);
5、char may(const char &);
6、template <typename T> void may(const T &);
7、template <typename T> void may(T *);
这里的4和7是匹配不到的,因为整数类型不能被隐式转换为指针类型。这里有几个函数的参数类型是进行过隐式转换过来进行匹配的。
通常函数调用的参数是可以与函数定义的参数进行转换之后进行匹配的,这里是按从最佳到最差的顺序排列:
1、完全匹配,常规函数优先于模板函数;
2、提升转换(例如:char和short会自动转换为int,而float会自动转换为double);
3、标准转换(例如:int转换为char,long转换为double);
4、用户定义的转换,如类声明中定义的转换。
完全匹配允许的无关紧要转换:
实参 -------- -> 形参
Type Type &
Type & Type
Type [ ] *Type
Type const Type
Type volatile Type
Type * const Type
Type * volatile Type *
代码示例
#include <iostream>
#include <stdlib.h>
template <typename T>
void showarray(T arr[],int n);
template <typename T>
void showarray(T *arr[],int n);
struct debts
{
char name[50];
double amount;
};
int main()
{
using namespace std;
int things[6] = {13,31,103,301,310,130};
struct debts mr_e[3]=
{
{"ima wolfe",2400.0},
{"urb foxe",1300.0},
{"iby scout",1800.0}
};
double *pd[3];
for (int i = 0; i < 3; i++)
pd[i] = &mr_e[i].amount;
cout << "list mr.e's count of thing:\n";
showarray(things,6);
cout << "list mr.e's debts:\n";
showarray(pd,3);
system("pause");
return 0;
}
template <typename T>
void showarray(T arr[],int n)
{
using namespace std;
cout << "template A\n";
for (int i = 0; i < n; i++)
cout << arr[i]<<' ';
cout << endl;
}
template <typename T>
void showarray(T *arr[], int n)
{
using namespace std;
cout << "template B\n";
for (int i = 0; i < n; i++)
cout << *arr[i] << ' ';
cout << endl;
}
演示效果