一.引入模板
如果现在需要定义一个通用的加法函数。
我们首先会想到函数重载:即针对每个所需相同行为的不同类型,重新实现它
#include<iostream>
using namespace std;
int Funtest1(const int& left,const int& right)
{
return left+right;
}
float Funtest2(const float& left,const float& right)
{
return left+right;
}
int main()
{
cout<<Funtest1(1,2)<<endl;
cout<<Funtest2(1.1,2.2)<<endl;
system("pause");
return 0;
}
这样的函数会有很多的缺点,比如:
*只要有新类型出现就会添加对应的代码
*除类型外,所有函数的函数体都相同,代码复用率不高
*如果函数只是返回值类型不同,函数重载不能解决。
*一个方法有问题,所有的方法都有问题,不好维护。
我们可能还会想到声明
#define ADD (left,right)?((a)+(b))
*但是声明的缺点就是不是函数,不进行类型检测,安全性不高。
##所以接下来我们就可以考虑使用函数模板
二.函数模板定义
<1>定义:函数模板代表了一个函数家族,该函数与类型无关
在使用时被参数化,根据实参类型产生函数的特定类型版本。
<2>格式:
template<typename Param1,typename Param2,...,typename Paramn>
返回值类型 函数名 (参数列表)
{
...
}
例如如下模板函数用来实现加法类函数
#include<iostream>
using namespace std;
template <class T>
T Add(T left,T right)
{
return left+right;
}
int main()
{
cout<<Add(1,2)<<endl;
cout<<Add(1.1,2.2)<<endl;
system("pause");
return 0;
}
在上述代码中如果我们想要对两个不同类型的数字进行加法运算,编译器就会报错,例如以下情况:
cout<<Add(10,10.0)<<endl;
为了想要其编译通过
则需要进行显示实例化
这样才会编译通过。
cout<<Add<int>(10,10.0)<<endl;
编译器只会执行两种转换:
- const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或指针来调用
例如:
以下情况在编译器里边是可以通过编译的:
#include<iostream>
using namespace std;
template <class T>
T Add(const T& i)
{
cout<<typeid(i).name()<<endl;
return i;
}
int main()
{
int i;
Add(i);
system("pause");
return 0;
}
- 数组或函数到指针的转换:数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针
例如:
#include<iostream>
using namespace std;
template <class T>
T Add(T i)
{
cout<<typeid(i).name()<<endl;//用来打印i的类型
return i;
}
void Funtest(int ,int)
{}
int main()
{
int arr[10];
Add(arr);
Add(Funtest);
system("pause");
return 0;
}
打印的结果为:
int *
void (__cdecl*)(int,int)
请按任意键继续. .
三.模板参数
- 所有模板的形参前面必须加class或者typename关键字进行修饰
例如
:
template <class T,U>//U前没有带关键字,编译会出错
T Add(T i,U u)
{
}
- 在函数的内部不能指定缺省的模板参数
- 模板的形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
例如
:
#include<iostream>
using namespace std;
typedef char T;
template <class T>
T Test1(T i)
{
cout<<"i.type="<<typeid(i).name()<<endl;
return i;
}
T t;
int main()
{
Test1(10);
cout<<"t.type="<<typeid(t).name()<<endl;
system("pause");
return 0;
}
打印出的结果为:
i.type=int
t.type=int
请按任意键继续…
故有此证明上文所述。
四.非模板类型参数
1.非模板类型形参是模板内部定义的常量,在需要常量表达式的时候可以使用非模板类型参数
例如:求数组长度
template <class T,int N>
void Test(T (&a)[N])
{
//可以实现对数组打印等操作
}
int main()
{
int array[10];
Test(array);
system("pause");
return 0;
}
其中N则代表了数组长度。
2.和函数参数表一样,跟多个参数必须用逗号隔开,实例化时候2个参数可以相同也可以不同
例如:
template <class T1,class T2>
void test(T1 i,T2 j)
{}
int main()
{
test(1,'1');
system("pause");
return 0;
}
由程序我们可以看到,test在传参的时候传了一个int型一个char型,但是编译器并没有报错。
五.模板函数的重载
例如:
我们需要写一个比大小的函数你可能会这么写:
template <class T>
const T& Max(const T& left,const T& right)
{
return left>right?left:right;
}
int main()
{
cout<<Max(1,2)<<endl;
system("pause");
return 0;
}
但是如果我需要比较三个数的大小,则编译器就会报错,解决这个问题的方法就是写一个函数与Max函数构成重载
const T& Max(const T& left,const T& mid,const T& right)
{
return Max(Max(left,mid),right);
}
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数还可以被实例化为这个非模板函数。
- 模板函数不允许自动类型转换,但普通函数可以自动类型转换。
例如:
如下函数
template <class T>
const T& Max(const T& left,const T& right)
{
return left>right?left:right;
}
template <class T>
const T& Max(const T& left,const T& mid,const T& right)
{
return Max(Max(left,mid),right);
}
const int & Max(int left,int right)
{
return left>right?left:right;
}
int main()
{
cout<<Max(3,6.7)<<endl;
system("pause");
return 0;
}
如果我们调试的时候跟进去会发现Max调用的是非模板函数
,如图:
程序运行的结果是:6
则可以验证上文说法。
六.模板函数的特化
以上函数重载虽然可以解决比较多个参数大小的问题,但是其解决不了比较两个字符串的问题,而只是将两个字符串的首元素的地址进行比较
这个时候我们可以进行函数的特化
template <class T>
const T& Max(const T& left,const T& right)
{
return left>right?left:right;
}
template <>//函数特化
char* const & Max<char*>(char* const& left,char* const& right)
{
if(strcmp(left,right)>0)
return left;
else
return right;
}
int main()
{
const char* p1="hello";
const char* p2="world";
cout<<Max(p1,p2)<<endl;
system("pause");
return 0;
}
输出是:hello