需要对多个不同类型的数据使用相同的算法时,可使用模板
函数模板是通用函数的描述,也就是说,他们使用泛型来定义函数。(通用编程)
//声明语法:
template <typename T> // or class T
void Swap(T& a, T& b);
//定义语法
template <typename T> // or class T
void Swap(T& a, T& b)
{
T temp; // 定义一个任意类型的变量T
temp = a;
a = b;
b = temp;
}
需要对不同的数据类型使用不同的算法时,可以使用重载的模板。
//声明语法:
template <typename T> // 模板1
void Swap(T& a, T& b);
template <typename T> // 模板2
void Swap(T* a, T* b, int n);
根据特征标(T& ,T&)和 (T* , T* ,int)来匹配不同的模板,实现模板的重载
//定义语法
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;
}
}
如果T为数组、指针或结构时,编写的模板函数很可能无法处理某些类型。而且这里有可能重载也会失效,重载并不是万能的,(重载要求参数特征标不同,但是当两个不同的算法特征标相同时是没办法进行重载的。)
这里有两种解决方案。
//常规模板
template <typename T>
void Swap(T& a, T& b);
struct job
{
char name[40];
double salary;
int floor;
};
// explicit specialization 显示具体化模板
template <> void Swap<job>(job& j1, job& j2);
//Swap中的是可选的,因为函数的参数类型表明,这是job的一个具体化
template <> void Swap(job& j1, job& j2);
//这样写也可以
实例化:常规模板只是一个模板而已,并没有在内存中实际生成的函数定义指令。
只有在发生函数调用时,再根据具体的参数类型来生成特定的函数定义,这叫隐式具体化。
也可以使用命令直接生成也定数据类型的函数定义,叫显式实例化这样直接链接即可使用,省去了隐式具体化所浪费的时间和空间。
显示实例化的语法:
template void Swap<int>(int ,int); //显式实例化一个int型函数
具体化(specialization):
区别:
//通用模板
template<class T>
void Swap(T &, T &);
//显式具体化
template<>void Swap<job>(job &, job &);
int main()
{
//显式实例化
template void Swap<char>(char &, char &);
short a,b;
...
//隐式实例化
Swap(a,b);
job n,m;
...
Swap(n,m); //使用显式具体化函数
char g,h;
...
Swap(g,h); //使用显式实例化
}
语法层面的区别 ,具体化比实例化多一个<>
//显式具体化声明 :template<>
//显式实例化声明: template
//通用模板 template /
用途层面的区别:
显式实例化是提前生成某个数据类型的函数定义,节省调用时间
显示具体化是指某个特殊类型单独定义,不使用通用模板。
这两个其实没什么关系,就是长得比较像而已,注意区分即可。
非模板函数 》 具体化模板 》 常规模板
指向非const数据指针和引用优先于分非const的指针和引用参数匹配。
(const和非const的区别只适用于指针和引用数据)
参数类型最匹配的,需要的转换最小的。
如果有多个最佳匹配,或没有匹配的函数,会报错。
// choices.cpp -- choosing a template
#include
template<class T>
T lesser(T a, T b) // #1 模板函数,返回a,b中较小值
{
return a < b ? a : b;
}
int lesser(int a, int b) // #2 非模板函数,返回a,b的绝对值的较小值
{
a = a < 0 ? -a : a;
b = b < 0 ? -b : b;
return a < b ? a : b;
}
int main()
{
using namespace std;
int m = 20;
int n = -30;
double x = 15.5;
double y = 25.9;
cout << lesser(m, n) << endl; // use #2 与模板函数和非模板函数都匹配,因此选择非模板函数
cout << lesser(x, y) << endl; // use #1 with double T = double ,与模板函数相匹配
cout << lesser<>(m, n) << endl; // use #1 with int lesser<>(m, n) 中的 <> 指出,使用模板函数,编译器注意到实参类型为int,因此选择T = int
cout << lesser<int>(x, y) << endl; // use #1 with int 这条语句要求进行显式实例化(使用int代替T),将使用显式实例化的得到的函数,
//x,y将被强制转换为int类型(15和25),所以这里会有一个警告,精度会丢失,结果是15而不是15.5
// cin.get();
return 0;
}
在编写模板函数时,有时候不能提前知道某个变量是那种类型。
template<class T1,class T2>
void ft(T1 X, T2 y)
{
?type? xplusy = x + y;
}
可以看到,这里我们没办法知道xplusy这个变量该定义什么样的数据类型,因为x和y的类型都是未知的,这时候c++11新增了decltype类型声明关键字 ,decltype(x+y)就代表一个和括号里(x+y)这个数据类型一样的一个类型。
使用语法如下
int x;
decltype(x) y; // y和x的数据类型相同
T1 x;
T2 y;
decltype(x+y) xplusy = x + y; //使xplusy的类型与x+y的类型相同
如需多次声明,可结合使用typedef和decltype
template<typename T1,typename T2>
void ft(T1 x,T2 y)
{
...
typedef decltype(x + y) xpytype;
xpytype xpy = x + y;
xpytype arr[10];
xpytype & rxy = arr[2];
...
}
decltype在实际使用时,编译器为了确定数据类型,必须要遍历一个和核对表,简化版的核对表如下
假设有如下声明
decltype(expression) var
第一步:如果expression是个没有用括号括起的限定符,则var与该标识符的类型相同,包括const等限定符。
double x = 5.5;
double y =7.69;
double & rx = x;
const double *pd;
decltype(x) w; //w is type double
decltype(rx) u = y;// u is type double &
decltype(pd) v; // pd is type const double *
第二步:如果expression是一个函数调用,则var的类型与函数的返回类型相同。(并不用真的调用一遍函数,编译器只需要查看一下函数原型声明的返回值类型即可)
long indeed(int , const int ); //function prototype
decltype(indeed(3,4)) m; // m is type long
第三步:expression是一个用括号括起来的标识符,(是一个左值),则var为指向其类型的引用。
(括号并不会改变表达式的值和左值性)
double xx = 4.4;
decltype((xx)) r2 = xx; // xx is type double &
decltype(xx) w = xx; // w is type double (第一步就匹配上了)
**第四部:**如果以上三步都不满足,则var类型与expression的类型相同
int j = 3;
int &k = j;
int &n = j;
decltype(j+6) i1; // i1 is type int
decltype(100l) i2; // i2 is type long
decltype(k+n) i3; // i3 is type int
//虽然 k和n都是引用类型,但是k+n不是引用,他是两个int的和,所以是int类型
template<typename T1,typename T2>
?type? gt(T1 x,T2 y)
{
...
return x+y;
}
这里无法预先知道x+y的类型,而使用decltype(x+y)也不行,因为此时x和y的参数还未具体声明,他们不在作用域内,编译器看不到他们,也无法使用他们。必须在声明参数之后才能使用decltype,不能提前使用。
C++11新增了后置返回类型,将返回类型移动到了参数声明后面。auto在这里是一个占位符,表示使用后置返回类型。
语法如下:
template<class T1,class T2> -> decltype(x + y)
{
...
return x + y;
}
现在,decltype在参数声明后面,因此x和y位于作用域内,可以使用它们。