模板总结
l 泛型编程
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。
模板分为,函数模板和类模板。
l 函数模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定的类型版本。
函数模板的的格式
template<typename T1,typename T2,......>
返回值类型 函数名(参数列表)
{
//......
}
例1:定义一个加法函数的函数模板;
template<typename T>
T Add(T left, T right)
{
return left + right;
}
typename 是用来定义模板参数的关键字,也可以使用class,建议尽量使用typename。
注意:不能使用struct代替typename。
函数模板也可以定义为inline函数
template<typename T>
inline T Add(T left, T right)
{
return left + right;
}
函数模板不是类或者函数,编译器用模板产生指定的类或者函数特定的函数,产生模板特定类型的过程称为函数模板的实例化。
例2:创建一个函数模板,实例化成不同的类型。
template<typename T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
cout<<Add(1, 2)<<endl;
cout<<Add('1', '2')<<endl;
cout<<Add(1.0, 2.3)<<endl;
return 0;
}
程序运行结果:3,c,3.3
程序分析:编译器在执行Add(1, 2)的时候会生成函数名为Add,返回值为int,函数的参数类型为int的函数。在执行Add('1', '2')的时候会生成函数名为Add,返回值为char,函数的参数类型为char的函数。在执行Add(1.0, 2.3)的时候会生成函数名为Add,返回值为double,函数的参数类型为double的函数。然后去调用。
注意:函数模板在编译期间被编译了两次。
1、实例化之前,检查模板代码本身,查看是否会出现语法错误。
2、在实例化期间,检查模板代码,查看是否所有的调用都有效。如:实例化类型不支持某些函数调用
从函数实现确定模板参数类型和值的过程称为模板实参推演。多个相同类型的实参必须完全匹配。
将上面的例题2改为,Add(1,’2’);编译器则无法根据函数的实参确定模板的实参,即就是编译器无法确定模板参数列表中的T是什么样的类型。如果想使上述代码通过编译,则应改为Add<int>(1,’2’)/Add<char>(1,’2’);即就是告诉编译器模板参数列表中的T是int或者是char。
编译器一般不会转化实参以匹配已有的实例化,相反会产生新的实例。在例题2中编译器在这里只会为我们生成不同的函数,不会去调用已经生成不同的函数,
编译器只会执行两种转化:
1、const转化:接收const引用或者const指针的函数分别用非const对象的引用或者指针来调用。
例题4:
template<typename T>
T Add(T& const left, T& const right)
{
return left + right;
}
int main()
{
int a = 1;
int b = 2;
cout<<Add(a, b)<<endl;
return 0;const
}
运行结果:3
程序分析:在T Add(T& const left, T& const right)语句中const修饰left,right,而实参的传递中a,b为非const的对象。故const类型对象引用可以用非const的对象来调用。
2、数组或函数到指针的转化:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规转化,数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。
例5:
template<typename T>
void Add(T* arr1)
{
cout << typeid(arr1).name() << endl;
}
int funt()
{
return 0;
}
template<typename T>
void fun(T(*p)())
{
p();
cout << typeid(p).name() << endl;
}
int main()
{
int(*p)()=funt;
fun(p);
int a[10] = { 0 };
Add(a);
return 0;
}
运行结果:int (_cdcel*)(void) int *
程序分析:主函数中指针p指向一个返回值为int,参数为void的一个函数,然后指针p作为函数模板fun的一个实参。fun函数内部打印指针p的类型,可发现它是一个函数指针,以此类推可发现a作为函数模板Add的实参的类型为int *。
l 模板形参
函数模板有两种类型的参数:模板参数和调用参数。在例题4中<typename T>为模板参数,(T& const left, T& const right)为调用参数。
模板参数分为类型形参和非类型形参。如:template<typename T,int a=0>,T为类型形参,a为非类型形参。模板形参的名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循屏蔽规则。在例题4中模板参数T只能在template<typename T>之后,函数Add的”}”之前使用,如果在函数模板之前存在变量T则在函数模板Add中T表示的template<typename T>中的T。
模板形参的名字在同一模板形参列表中只能使用一次。如:template<typename T>中不能定义另外一个T即就是template<typename T,typename T>这种形式是错误的。
所有模板形参前面必须加上class或者typename关键字修饰。在函数模板的内部不能使用指定缺省的模板实参,即就是T Add(T& const left=0, T& const right=0)。
非模板类型形参是模板内部定义的常量,在需要表达式的时候可以使用非模板类型形参。
例6:
template<typename T,int a>
void Add(T (&arr1)[a])
{
cout << typeid(arr1).name() << endl;
cout << a << endl;
}
int main()
{
int a[10] = { 0 };
Add(a);
return 0;
}
运行结果:int* 10
程序分析;数组a作为函数模板的实参被当作数组首元素地址故类型为int*。模板参数中的a被当作数组的长度故等于10;
模板形参说明:
1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同。
3、模板形参表不能为空。
4、模板形参可以时类型形参,也可以是非类型形参。类型形参跟在class和typename后。
5、模板形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转化。
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为c++标准加入到C++中的,旧的编译器可能不支持。
l 函数模板的重载
例7:
int Max(const int& a, const int& b)
{
return a > b ? a : b;
}
template<typename T>
T Max(T const& a, T const &b)
{
return a > b ? a : b;
}
template<typename T>
T Max(T const& a, T const &b, T const &c)
{
return Max(Max(a, b), c);
}
int main()
{
Max(1, 2);
Max(1.0, 2.0);
Max(1, 2, 3);
return 0;
}
程序分析:一个非模板函数可以和一个同名的函数同时存在,而且该函数模板还可以被实例化为这个非模板函数;对于模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板;显示指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用;模板函数不允许自动类型转化,但普通函数可以进行自动类型转换。
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
l 模板函数的特化
有时候并不总是能够写出对所有可能被实例化的类型都是最合适的模板,在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情。
模板函数特化形式如下:
1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号指定这个特化定义的模板形参
3、函数形参表
4、函数体
template<>
返回值 函数名<Type>(参数列表)
{
//函数体
}
例8:函数模板的特化
T Max(T const& a, T const &b)
{
return a > b ? a : b;
}
template<>
char Max<char>(const char&a, const char&b)
{
return a;
}
程序分析:在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
l 模板类
模板类格式:
Template<class 形参名1,class 形参名2……>
Class 类名
{.......};
例9:声明一个模板类
template<typename T>
class Seqlist
{
public:
Seqlist()
:_pHead(NULL),
_pTail(NULL),
size(0)
{}
private:
T* _pHead;
T* _pTail;
int size;
};
模板类的实例化,只要有一种不同的类型,编译器就会实例化出一个对应的类。如对例题9中的模板类实例化:Seqlist<int> list;Seqlist<double> list2;
当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模板形参,重新编写Seqlist类,最后创建名为Seqlist<int>和Seqlist<double>的类。
类模板的特化
例10:对例9的类进行全特化
template<typename T>
class Seqlist
{
public:
Seqlist()
:_pHead(NULL),
_pTail(NULL),
size(0)
{}
private:
T* _pHead;
T* _pTail;
int size;
};
template<>
class Seqlist<int>
{
public:
Seqlist()
:_pHead(NULL),
_pTail(NULL),
size(0)
{}
private:
int* _pHead;
int* _pTail;
int size;
};
例11:偏特化
template<typename T,typename T2>
class Seqlist
{
public:
Seqlist()
:_pHead(NULL),
_pTail(NULL),
size(0)
{}
private:
T* _pHead;
T* _pTail;
int size;
};
//对模板形参偏特化成两个指针
template<typename T, typename T2>
class Seqlist<T*,T2*>
{
public:
Seqlist()
:_pHead(NULL),
_pTail(NULL),
size(0)
{}
private:
T _pHead;
T2 _pTail;
int size;
};
l 模板的分离编译
模板分离编译链接出错原因:
解决办法:
1、在模板头文件xxx.h里面显示实例化---->模板类的定义后面添加template class Seqlist<int>;一般不推荐这种方法,一方面老编译器可能不支持,另一方面实例化依赖调用者。
2、将声明和定义放到一个文件“xxx.hpp”里面。
l 模板总结
优点:模板增加了代码的复用性,节省资源,更快的迭代开发,C++标准模板库(STL)因此产生,增强代码的灵活性。
缺点:模板让代码变得凌乱复杂,不易维护,编译代码时间变长;出现模板编译错误时,错误信息非常凌乱,不易定位错误,