本文一下所涉及到的代码都是在vs2008下实现的,本文只是对模板的一个初步了解,未做深入剖析
模板(Templates)是ANSI-C++ 标准中新引入的概念。如果你使用的 C++ 编译器不符合这个标准,则你很可能不能使用模板。模板(template)分为函数模板和类模板,本文主要介绍函数模板。
函数模板(Function Template)
模板可以让我生成通用的函数,这些生成的函数可以接受任意数据类型的参数,可以返回任意的类型
函数模板代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板的格式
template <typename T,typename t2......class Tn>
返回值类型 函数名(参数列表)
{
.....
}
例如:
#include<iostream>using namespace std;
template <class T> //模板形参名字可以随意定义
T Add(T left , T right)
{
return (left+right);
}
int main(){
cout<<Add(2,3)<<endl; // 5
cout<<Add('2','3')<<endl; // f
return 0;}
调用Add给我们生成了两个Add函数,一个参数和返回值为int的Add函数和一个参数和返回值为char的函数。
在第3行声明中,我们已经生成了一个通用数据类型的模板,叫做T。因此在其后面的函数中,T 成为一个有效的数据类型, 它被用来定义了两个参 数 left 和 right ,并被用作了函数 Add 的返回值类型,T 不代表任何具体的类型,当函数 Add被调用时,我们可以使用任何有效的数据类型来调用它,也就是说实参传给模板形参什么类型的数据,T就是什么类型,此处是个int 型,
当函数 GetMax 被调用的时候,我们可以使用任何有效的数据类型来调用它。这个数据类型将被作为pattern来代替函数中GenericType 出现的地方。用一个类型pattern来调用一个模板的方法如下:
function<type>(paramsters)
例如,要调用Add来比较两个int类型的整数可以这样写:
int x,y;
Add<int >(x,y);
因此,Add的调用就像所有的 T 都被int 替换了一样;显示的实例化
当我们传入的参数如下:
cout<<Add(2,'3')<<endl; //会报错 模板 参数“T”不明确
仅靠前面的template<typename T> T Add(T left,T right)模板函数是做不到的,模板函数不允许自动类型转换在这个地方编译器不知道T为哪种类型,可能为char 也可能是int; 解决方法如下:
template <typename T1, typename T2>
T1 Add(T1 left,T2 right)
{
cout<<typeid(T1).name()<<" " ; //输出T1的类型
cout<<typeid(T2).name()<<endl;
return left+right;
}
int main()
{
cout<<Add(2,3)<<endl;
cout<<Add(2,'3')<<endl;
return 0;
}
模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
例如:
template <class T>
T Add(T left,T right)
{
T a = 1; //定义一个变量
return left+right+a;
}
int main()
{
cout<<Add(2,3)<<endl; // 6
return 0;
}
typename是用来定义模板参数的关键字,也可以用class,尽量使用typename,
注意不可以是用struct;
模板函数也可以定义为内联(inline)函数
template <class T>
inline T Add(T left,T right) //inline关键字必须放在模板参数列表之后,函数放回值之前,不能放在template之前
{
return (left+right);
}
模板形参的名字在同一模板形参列表中只能使用一次
所有模板形参前面必须加上class或者typename关键字修饰
template<class T,class T>
T Add(T left, T right)
{
return left +right;
}
重定义 模板 参数“T”
template<class T, U>
T Add(T left, U right)
{
return left +right;
}
语法错误 : 标识符“U”
语法错误 : 标识符“U”
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
如上面的cout<<Add(2,'3')<<endl;不会将‘3’转化为int型去调用Add(int ,int),而是产生了一个新的实例。
编译器只会执行两种转换:
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指
针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针(可以自己上机试试)
【非模板类型参数】
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。
例如:数组长度
template <typename T,int N>
void Funtest(T (&arr)[N])
{
for(int idx = 0; idx< N; ++idx)
{
arr[idx] = idx;
}
}
int main()
{
int a[5];
int b[10];
Funtest(a);
Funtest(b);
for(int idx = 0;idx<sizeof(a)/sizeof(a[0]);++idx)
cout<<a[idx]<<" ";
cout<<endl;
for(int idx = 0;idx<sizeof(b)/sizeof(b[0]);++idx)
cout<<b[idx]<<" ";
cout<<endl;
return 0;
}
注意:在函数模板的内部不能指定缺省的模板实参。
总结一下
1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、模板形参表不能为空(模板特例化除外,后面会有介绍)
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
模板函数的重载
int Max(int left, int right)
{
return (left<right)?right:left;
}
template<typename T>
T Max( T left, T right)
{
return (left<right)?right:left;
}
template<typename T>
T Max(T a, T b, T c)
{
return Max(Max(a,b),c); //调用int Max(int left, int right)
}
int main()
{
Max(10, 20, 30); //调用template<typename T>T Max(T a, T b, T c)
Max<>(10, 20); //调用template<typename T>T Max( T left, T right)
Max(10, 20); //调用int Max(int left, int right)
Max(10, 20.12); //调用 int Max(int left, int right)类型发生隐式类型转化
Max<int>(10.0, 20.0); //调用template<typename T>T Max( T left, T right) <int>指明只能由模板函数生成的的函数
Max(10.0, 20.0); //调用template<typename T>T Max( T left, T right)
return 0;
}
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【说明】
1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
模板函数特化
有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事。
例如上面的代码可以比较字符串的大小?很显然是不可以的
template <class T>
int Compare( T t1, T t2)
{
if(t1<t2)
return -1;
if(t1>t2)
return 1;
return 0;
}
int main()
{
char *p1 = "djklf";
char *p2 = "fjkqw";
cout<<Compare(p1,p2)<<endl; //1
cout<<Compare(p2,p1)<<endl; // -1
return 0;
}
为什么会出现这种情况呢?
可以看出在Compare函数中比较的是p1,和p2的地址大小
可以这样做
template <class T>
int Compare( T t1, T t2)
{
if(t1<t2)
return -1;
if(t1>t2)
return 1;
return 0;
}
template<>
int Compare<const char*>(const char* p1, constchar* p2) //在函数模板实例化的函数的const修饰的是指针p1和 //p2所指向空间的内容,所以传的参数必须是const char *的
{
return strcmp(p1,p2);
}
int main()
{
const char *p1 = "djklf";
const char *p2 = "fjkqw";
cout<<Compare(p2,p1)<<endl; //调用template<>int Compare<const char*>(const char* const p1, const //char* p2)
return 0;
}
给他特例化出一个模板函数
模板函数特化形式如下:
1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3、函数形参表
4、函数体
template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}
假如少了模板形参表
只是定义了一个普通函数,该函数含有返回类型和与模板实例化相匹配的形参表。
注意:在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,
如果不匹配,编译器将为实参模板定义中实例化一个实例。
注意:特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然
后使用该特化版本的每个源文件包含该头文件。