一、函数模板
1、定义:
代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2、格式:
template<typename/classT>//但是建议使用typename,避免和class类混淆
Template <类型1变量1, 类型2 变量2, ….. > 返回类型 函数名(形参表)
{
函数定义体;
}
3、作用域
typedef intT;
template <typenameT>
T Add(Tleft,Tright)
{
T value = 3.14;//此处的value为double类型,局部隐藏了typedef定义的全局T;
cout<< typeid(value).name() << endl;
return(left+right);
}//T的作用域在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则。
T global ;此处的T是全局的typedef的 int T;
4、编译:
模板被一共被编译了两次:
第一阶段:实例化之前,检查模板代码本身,看看模板是否出现语法错误,如:忘记分号或者变量名拼错,并检查参数类型是否匹配,在此阶段,编译器只能检查部分的错误。
第二阶段:实例化期间,只有这个阶段才会发现类型相关的错误,例如:实例化类型不支持某些函数调用。
5、实例化:
template <typenameT>
T Add(T left, T right)
{
return(left+ right);
}
cout << Add('1', '2')<<endl;//c
cout << Add<int>(1, '1') <<endl;//49+1=50
cout << Add(1, '1') << endl;//编译不通过
二.模板形参:(类型形参和非类型形参)
类型形参:
1、类型形参转换:( 编译器只会执行两种转换)
(1)const转换:接受const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用。
template <typenameT>
void FunTest(constT&p)//本来应该是const int *p而实际是int*const p,
{
int b = 10;
p = &b;//编译错误,试图修改p的值
cout << *p << endl;
}
(2)数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向第一个元素的指针,函数实参当做指向函数类型的指针.
void FunTest1()
{
cout<< "FunTest()"<< endl;
}
template <typenameT>
void FunTest(T p)
{
cout<< typeid(p).name() << endl;
}
int arr[10];
FunTest(arr);//运行结果:int*
FunTest(FunTest1);//运行结果:void <__cdecl*><void>
注意:
1、模板形参的名字在同一模板形参列表中只能出现一次。
2、模板形参用<>括起来,且模板形参不能为空,多个形参之间必须加“,”。
3、模板形参可以是类型形参和非类型形参,类型形参必须使用关键字class或typename。
4、使用模板类型形参和内置类型或者自定义类型使用方法完全一样,可用作指定函数形参类型,返回类型,局部变量和强转类型转换。
非模板类型参数:
1、概念:
非类型模板参数,顾名思义,模板参数不限定于类型,普通值也可作为模板参数。它是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型。而使用基于常量的模板时,你必须显式地指定这些值,模板方可被实例化。
2、用途:
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或引用。
绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期。
3、例如:
template<typenameT,int n>
void funtest(T(&array)[n])
{
for(inti = 0; i < n; i++)
{
array[n]= 0;
}
}
template<unsignedN,unsigned M>
int compare(constchar(&p1)[N],constchar(&p2)[M])
{
returnstrcmp(p1, p2);
}
int_array[5];
funtest(_array);
/*
000434FC call funtest<int,5> (041528h)
*/
char_array1[5];
funtest(_array1);
/*
00234518 call funtest<char,5> (023152Dh)
*/
compare("hi", "hello");
/*编译器会按照字面常量的大小来代替N和M,从而实例化模板,
注意:
编译器会在一个字符串字面常量的末尾插入一个空字符作为终结符,
因此编译器会实例化处以下版本:
intcompare(const char(&p1)[3], const char(&p2)[6])
Ps:非类型模板参数的模板必须是常量表达式。
*/
三、模板函数重载
类似于通函数一样,模板函数也可以重载。(注意:模板函数既不是函数也不是类)
int _max(intleft,int right)
{
returnleft >right ? left : right;
}
template <typenameT>
T _max(const T&left, const T & right)
{
cout<< typeid(left).name() << endl;
cout<< typeid(right).name() << endl;
returnleft >right?left:right;
}
template <typenameT>
T _max(T left, T mid,T right)
{
T temp =_max(left, mid);
returntemp>right?temp:right;
}
int main()
{
_max(3,5);//优先调用非模板函数,而不会从该模板产生一个实例
_max<>(3,5);//显示指定一个空的模板实参列表,告诉编译器只能是模板才能匹配这个调用
_max<>(3,5, 6); //调用的是模板函数,与_max(3, 5)构成重载
_max(2,4.5);//输出4,编译器调用的是非模板函数(普通函数),
//因为模板函数不允许自动类型转换,但是普通函数可以进行自动类型转换
_max<int>(2,4.5);//输出4,调用的是模板函数,显示将函数参数都转换为int类型
//_max<>(3,4.3 ) ;//编译不通过,模板函数只有一个T类型,而模板尝试实例化这个函数时,却遇到两个不同类型参数,
//模板不允许自动类型转换,所以报错
return0;
}
四、模板函数特化
1、原因:
当函数模板需要对某些类型进行特别处理,称为函数模板的特化。因为,在某些情况下,通用模板定义对于某个类型可能是错误的,或者编译通过不了,即使编译通过了,也不是我们想要得到的答案,这时候,需要我们对函数模板进行特化。
2、特化格式:
template<>
返回值 函数名<type> (函数参数列表){函数体 }
3、例如:
template<typenameT>//普通函数模板
int is_Equal(T t1, T t2)
{
if(t1 < t2) return -1;
if(t1>t2) return 1;
return0;
}
template<>//特化函数模板
int is_Equal<constchar*>(constchar*constp1,constchar*constp2)
{
returnstrcmp(p1, p2);
}
const char*p1 = "bbbb";
const char*p2 = "aaaa";
cout<< is_Equal( p1, p2) << endl;//p1>p2应该返回1,而实际结果却是-1;
//p1 0x0108dc70 "bbbb" char *
//p2 0x0108dc78 "aaaa" char *
//比较时,直接比较的是两个地址的大小,而没有比较两个指针的内容
4、特化位置:
特化必须出现在模板实例的调用之前,否则在编译的时候,找不到特化函数模板,则会按照普通模板函数进行调用。