何为模板?
在谈模板之前,我们先来说一下如何写一个通用的加法函数?
1、函数重载,针对不同的类型去分别实现
int Add(int left,int right)
{
return left+right;
}
char Add(char left,char right)
{
return left+right;
}
double Add(double left,double right)
{
return left+right;
}
但是,只要有新类型出现,就得重新添加实现函数,代码复用率不高,牵一发动全身,不好维护。
2、使用公共基类,将通用的代码放在公共的基础类里面
此方法借助公共基类来编写通用代码,将失去类型检查的优点;
对于以后实现的许多类,都必须继承自某个特定的基类,代码维护更加困难。
3、宏
#define ADD(a,b)((a)+(b))
但是宏不可调式,不进行类型检测,安全性不高,有可能产生副作用
4、泛型编程
所谓泛型编程就是编写与类型无关的代码,是代码复用的一种手段,而模板是泛型编程的基础,但它本身不是一个函数或者类。
模板分为函数模板和类模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
模板函数的格式
template<typename Param1, typename Param2,......>
返回值类型 函数名(参数列表)
{}
例:template<typename T>
T Add(T left,T right)
{
return left+right;//此处不写分号,程序编译时是检测不出来的
}
说明:
模板参数列表中也可以使用class,但不能使用struct;
模板函数的参数列表中不能使用缺省参数;
模板函数也可以定义为inline函数
template<typename T>
inline T Add(const T left, const T right)
{
return (left + right);
}
注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前
函数模板实例化:编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程。
template<typename T>
T Add(T left,T right)
{
return left+right;
}
//1、int Add(int left,int right)
//{
//return left+right;
//}
//2、char Add(char left,char right)
//{
//return left+right;
//}
//3、double Add(double left,double right)
//{
//return left+right;
//}
int main()
{
cout<<Add(1,2)<<endl;//1
cout<<Add('1','2')<<endl;//2
cout<<Add(1.1,2.2)<<endl;//3
cout<<Add<int>(1,'2')<<endl;//1
cout<<Add(<char>1,'2')<<endl;//2
system("pause");
return 0;
}
注意:这里模板被编译了两次:
1、实例化之前,检查模板代码本身,查看是否出现语法错误,如遗漏分号/
2、在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用
【类型形参转换】
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指
针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
模板参数:
函数模板有两种类型参数:模板参数和调用参数
1、模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
typedef int T;
template <typename T>
void fun(T p)
{
cout<<typeid(p).name()<<endl;//double
}
T a;
int main()
{
fun(3.3);
cout<<typeid(a).name()<<endl;//int
system("pause");
return 0;
}
2、模板形参的名字在同一模板形参列表中只能使用一次
3、所有模板形参前面必须加上class或者typename关键字修饰
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数
模板形参说明
1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、模板形参表不能为空
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
模板函数重载
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前
int Max(const int& left,const int & right)//非模板函数
{
return left>right? left:right;
}
//模板函数
template<typename T>
T Max(const T& left, const T& right)
{
return left>right? left:right;
}
int main()
{
Max<>(1, 2);
Max(1, 2);//优先调用非模板函数
Max<int>(1.0, 2.0);
Max(1.0, 2.0);
return 0;
}
说明:
1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例
化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板
函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
但是,模板函数并不能解决所有的情况,例如上边的模板就不能比较两个字符串。
此时,就可以使用模板函数特化,函数模板特化的形式如下:
1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3、函数形参表
4、函数体
template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}
template<typename T>//模板函数
T Max(const T& left, const T& right)
{
return left>right? left:right;
}
template<>//模板函数特化
char* Max< char *>( char* const & left, char* const & right)//const 修饰left
{
if(strcmp(left,right)==0)
{
return left;
}
return right;
}
int main()
{
Max(1, 2);
Max(1.0, 2.0);
char* str1= "hello";
char* str2 = "world";
Max(str1,str2);
return 0;
}
注意:在模板特化版本的调用中,实参类型必须与特化版本函数的形 参类型完全匹配,如果不匹配,编译器将为实参模板定义中实 例化一个实例。
特化不能出现在模板实例的调用之后,应该在头文件中包含模 板特化的声明,然后使用该特化版本的每个源文件包含该头文 件。
模板类
模板类也是模板,必须以关键字template开头,后接模板形参表。
template<class 形参名1, class 形参名2, ...class 形参名n>
class 类名
{ ... };
//普通顺序表:
typedef int DataType;
//typedef char DataType;
class SeqList
{
private :
DataType* _data ;
int _size ;
int _capacity ;
};
模板类顺序表:
template<typename T>
class SeqList
{
private :
T* _data ;
int _size ;
int _capacity ;
};
类模板的特化:
//全特化
template <typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
template<typename T>
SeqList <T>:: SeqList()
: _size(0)
, _capacity(10)
, _data(new T[ _capacity])
{
cout<<"SeqList<T>" <<endl;
}
template<typename T>
SeqList <T>::~ SeqList()
{
delete[] _data ;
}
template <>
class SeqList <int>
{
public :
SeqList(int capacity);
~ SeqList();
private :
int _size ;
int _capacity ;
int* _data ;
};
// 特化后定义成员函数不再需要模板形参
SeqList <int>:: SeqList(int capacity)
: _size(0)
, _capacity(capacity )
, _data(new int[ _capacity])
{
cout<<"SeqList<int>" <<endl;
}
// 特化后定义成员函数不再需要模板形参
SeqList <int>::~ SeqList()
{
delete[] _data ;
}
void test1 ()
{
SeqList<double > sl2;
SeqList<int > sl1(2);
}
偏特化(局部特化)
template <typename T1, typename T2>
class Data
{
public :
Data();
private :
T1 _d1 ;
T2 _d2 ;
};
template <typename T1, typename T2>
Data<T1 , T2>::Data()
{
cout<<"Data<T1, T2>" <<endl;
}
// 局部特化第二个参数
template <typename T1>
class Data <T1, int>
{
public :
Data();
private :
T1 _d1 ;
int _d2 ;
};
template <typename T1>
Data<T1 , int>::Data()
{
cout<<"Data<T1, int>" <<endl;
}
模板
1.优点:模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
增强了代码的灵活性。
2.缺点:模板让代码变得凌乱复杂,不易维护,编译代码时间变长。出现模板编译错误时,错误信息非常凌乱,不易定位错误。