问: 什么是模板?
模板是一个蓝图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化
模板:函数模板和类模板
模板函数的格式:
template<typename Param1, typename Param2, ...,class Paramn>
返回类型 函数名(形参列表)
//模板形参名字可以任意命名
{
//函数体
}
**注: 1.typename是用来定义模板参数关键字,也可以使用class,建议尽量使用typename
2.不能用struct代替typename
class和struct的不同点:
1.访问权限不同 2.继承权限不同 3.定义模板参数是 struct不可用
模板函数也可以定义为inline函数
typename<typename T>
inline T Add(const T _left, const T _right)
{
return (_left + _right);
}
**注: inline关键字必须放在模板形参之后,返回值之前,不能放在template之前
下面来看一个例子:
int Add(int left, int right)
{
return (left+right);
}
double Add(double left, double right)
{
return (left+right);
}
template<typename T>
T Add(T left, T right)
{
return (left+right);
}
int main()
{
cout<<Add(10, 20)<<endl; //int
cout<<Add(12.34, 34.12)<<endl; //double
cout<<Add(10, (int)34.12)<<endl;//隐式实例化----强转
cout<<Add<int>(10, 34.12)<<endl;//显式实例化
}
**注意:模板被编译了两次
1 实例化之前,检查模板代码本身,查看是否出现语法错误。eg:遗漏分号
2 在实例化期间,检查模板代码,查看是否所有的调用都有效。eg:实例化类型不支持某些函数调用
【类型形参转换】
一般不会转换实参以匹配已有的实例化,相反会产生新的实例
编译器只会执行两种转换:
1.const转换:接受const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2.数组或者函数到指针的转换:如果模板形参不是引用类型,则对数组或者函数类型的实参应用常规指针转换,数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针
代码实现:
template<typename T>
void Test(const T& a)
{}
int main()
{
int b = 0;
Test(b);
int a[] = {0};
Test(a);
return 0;
}
数组:
a的类型:int const[];去掉&后:int*
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础
一、函数模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
函数模板有两种类型参数:模板参数和调用参数
*1.1模板参数*
(1)模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
下面我们来看一段代码:
typedef int T; //重定义
template<class T> //模板参数
void Test(T t/*调用参数*/)
{
cout<<"t Type = "<<typeid(t).name()<<endl;
};
T g;
int main()
{
Test(10);
cout<<"g Type = "<<typeid(g).name()<<endl;
system("pause");
return 0;
}
(2)模板形参的名字在同一模板参数列表中能使用一次
(3)所有模板形参前面必须加上class或者typename关键字修饰
注意:在函数模板的内部不能指定缺省的模板实例
【总结】
1.模板形参表使用<>括起来
2.和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不同
3.定义模板函数时模板形参表不能为空
4.模板形参可以是类型形参,也可以是非类型形参,类型形参跟在class和typename之后
5.模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型和自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6.模板形参表中,class和typename具有相同含义,可以互换,使用typename更加直观,但关键字体typename是作为C++标准加入到C++中的,旧的编译器可能不支持
*1.2非模板类型参数*
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数
例如数组长度:
template<typename T, int N> //N不同调用函数也不同
void Test(T (&_array)[N])
{
for(int i=0; i<N; ++i)
{
_array[i] = 0;
}
}
int main()
{
int a[5];
float b[5];
Test(a);
Test(b);
return 0;
}
【小结】
1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数,而不会从该模板产生一个实例。若模板产生了一个具有更好匹配的函数,那么将选择模板
3.显示指定一个空的模板实参列表,该语法告诉编译器只有模板才能匹配这个调用,而且所有的模板参数都应该根据实参演绎出来
4.模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
二、函数模板特化
先来看一段代码:
// 函数1
int Max(const int& left,const int& right)
{
return left>right ? left:right;
}
// 函数2
template<typename T>
T Max(const T& left,const T& right) //传T的引用(节省空间,提高效率)
{
return left>right ? left:right;
}
// 函数3
template<typename T>
T Max(const T& a,const T& b,const T& c) //传T的引用(节省空间,提高效率)
{
return Max(Max(a, b), c);
}
int main()
{
Max(10, 20, 30); //调函数3
Max(10, 20); //调函数1
Max(10.0, 20.0); //调函数2
Max(10, 10.2); //调函数1
Max<>(10, 20); //调函数2
Max<int>(10, 10.2); 显式实例化 //调函数2
return 0;
}
由上述例子可以看出: Max<>(10, 20)和Max(10, 10.2)调同一个模板函数
*思考:上面的模板可以比较两个字符的大小吗?
答: 当然不可以,它会比较成两个地址的大小。
有时候并不能总是写出对所有可能被实例化的类型都合适的模板,当然在某些情况下,通用模板定义对于某个类型可能完全错误,或者不能编译,这个时候就可以引进特化模板函数。
**模板函数特化形式:
1.关键字template后面接一对空的尖括号<>
2.函数名后面接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3.函数形参表
4.函数体
template<>
返回值函数名<Type>(参数列表)
{
//函数体
}
**注:
(1)特化的声明必须与特定的模板相匹配,否则会报错
(2)如果少了模板形参表,则就只是定义了一个普通函数,该函数含有返回类型和与模板实例化相匹配的形参表
(3)在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例
/让我们来看比较字符串的代码///
template<typename T>
int compare(T t1, T t2) 在函数调用时直接将两个指针变量的地
{ 址传递给模板函数,在比较时,直接比
if(t1 < t2) 较的是两个地址的大小,没有比较指针
return -1; 的内容
if(t1 > t2)
return 1;
return 0;
}
int main()
{
char *str1 = "1 2 3 4";
char *str2 = "a b c d";
cout<<compare(str1, str2)<<endl;
return 0;
}
也可以这样定义
template<>
int compare<const char*>(const char* const p1, const char* const p2)
{
return strcmp(p1, p2);
}
升级版:
template<typename T>
int compare(T t1, T t2)
{
if(t1 < t2)
return -1;
if(t1 > t2)
return 1;
return 0;
}
int compare(char* t1, char* t2)
{
if(t1 < t2)
return -1;
if(t1 > t2)
return 1;
return 0;
}
template<>
int compare<const char*>(const char* const p1, const char* const p2)
{
return strcmp(p1, p2);
}
int main()
{
const char *str1 = "1 2 3 4";
const char *str2 = "a b c d";
char *str3 = "1 2 3 4";
char *str4 = "a b c d";
cout<<compare(str1, str2)<<endl;
cout<<compare(str3, str4)<<endl;
system("pause");
return 0;
**注意:
特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头文件