类模板(class Template)函数模板定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
声明类模板:
template
//TODO;
}
类模板和函数模板都是以 template 开头(当然也可以使用 class,目前来讲它们没有任何区别),后跟类型参数;类型参数不能为空,多个类型参数用逗号隔开。一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
区分类模板与模板类的概念
一个类模板(类生成类)允许用户为类定义个一种模式,使得类中的某些数据成员、默认成员函数的参数,某些成员函数的返回值,能够取任意类型(包括系统预定义的和用户自定义的)。
如果一个类中的数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为模板,它的存在不是代表一个具体的、实际的类,而是代表一类类。
#include
#include
using namespace std;
template
class Test{
public:
void add(T1 a,T2 b){
cout<<"void add(T1 a,T2 b)"<
class Test{
public:
void add(T1* a,T2* b){
cout<<"void add(T1* a,T2* b)"<
class Test{ //Test类模板的两个类型参数完全相同时,使用这个实现
public:
void add(T a,T b){
cout<<"void add(T a,T b)"<"<//没有泛指类型
class Test{
public:
void add(void* a,void* b){
cout<<"void add(void* a,void* b)"< t1; // 使用第一个类模板;
Test t2; // 使用第三个类模板,特化实现;
Test t3; // 使用第四个类模板,特化实现;
t1.add(1, 2.5); // void add(T1 a, T2 b) 3.5;
t2.add(5, 5); // void add(T a, Tb) 10;
t2.print(); // class Test < T, T >
t3.add(NULL, NULL); // void add(void* a, void* b);Error to add void* param...;
Test t4; // 未有定义指针特化时,编译器显示 14 行:error: invalid operands of types 'int*' and 'double*' to binary 'operator+';
// 特化指针后,打印 void add(T1* a, T2* b);
int a = 1;
double b = 0.1;
t4.add(&a, &b); // 1.1
return 0;
}
1,类模板的特化实现表象上面好像定义了不同的类模板,但其实我们仅仅是根据需要将一个类模板分开成不同的情况来实现;
2,编译器编译过后根据我们使用的类型参数来决定究竟是哪一种实现;
类模板三种特化:
模板参数的类模板特化的几种类型, 一是特化为绝对类型; 二是特化为引用,指针类型;三是特化为另外一个类模板。
template
class Compare{
public:
static bool IsEqual(const T& lh,const T& rh){
return lh==rh;
}
};
(一)特化为绝对类型
也就是说直接为某个特定类型做特化,这是我们最常见的一种特化方式, 如特化为float, double等
template<>
class Compare{
public:
static bool IsEqual(const float& lh,const float& rh){
return abs(lh-rh)<10e-3;
}
};
template<>
class Compare{
public:
static bool IsEqual(const double& lh,const double& rh){
return abs(lh-rh)<10e-6;
}
};
(二)特化为引用,指针类型
这种特化我最初是在stl源码的的iterator_traits特化中发现的
template
struct iterator_traits {
typedef typename _Iterator::iterator_category iterator_category;
typedef typename _Iterator::value_type value_type;
typedef typename _Iterator::difference_type difference_type;
typedef typename _Iterator::pointer pointer;
typedef typename _Iterator::reference reference;
};
// specialize for _Tp*
template
struct iterator_traits<_Tp*> {
typedef random_access_iterator_tag iterator_category;
typedef _Tp value_type;
typedef ptrdiff_t difference_type;
typedef _Tp* pointer;
typedef _Tp& reference;
};
// specialize for const _Tp*
template
struct iterator_traits {
typedef random_access_iterator_tag iterator_category;
typedef _Tp value_type;
typedef ptrdiff_t difference_type;
typedef const _Tp* pointer;
typedef const _Tp& reference;
};
除了T*, 我们也可以将T特化为 const T*, T&, const T&等,以下还是以T*为例:
template
class Compare{
public:
static bool IsEqual(const T* lh,const T* rh){
return Compare::IsEqual(*lh,*rh);
}
};
特化其实是就不是一种绝对的特化, 它只是对类型做了某些限定,但仍然保留了其一定的模板性,这种特化给我们提供了极大的方便, 如这里, 我们就不需要对int*, float*, double*等等类型分别做特化了。
(三)特化为另外一个类模板
第二种方式的扩展,其实也是对类型做了某种限定,而不是绝对化为某个具体类型
template
class Compare >
{
public:
static bool IsEqual(const vector& lh, const vector& rh)
{
if(lh.size() != rh.size()) return false;
else
{
for(int i = 0; i < lh.size(); ++i)
{
if(lh[i] != rh[i]) return false;
}
}
return true;
}
};
把IsEqual的参数限定为一种vector类型, 但具体是vector
将其“半特化”为任何我们自定义的模板类类型:
template
struct SpecializedType
{
T1 x1;
T1 x2;
};
template
class Compare >
{
public:
static bool IsEqual(const SpecializedType& lh, const SpecializedType& rh)
{
return Compare::IsEqual(lh.x1 + lh.x2, rh.x1 + rh.x2);
}
};
三种类型的模板特化, 我们可以这么使用这个Compare类:
// double
double d1 = 10;
double d2 = 10;
bool r3 = Compare::IsEqual(d1, d2);
// pointer
int* p1 = &i1;
int* p2 = &i2;
bool r4 = Compare::IsEqual(p1, p2);
// vector
vector v1;
v1.push_back(1);
v1.push_back(2);
vector v2;
v2.push_back(1);
v2.push_back(2);
bool r5 = Compare >::IsEqual(v1, v2);
// custom template class
SpecializedType s1 = {10.1f,10.2f};
SpecializedType s2 = {10.3f,10.0f};
bool r6 = Compare >::IsEqual(s1, s2);
类模板特化注意:
1.特化只是模板的分开实现
本质上是同一个类模板,仅仅是将模板根据需要分开实现
2.特化类模板的使用方式是同一的
必须显示指定每一个类型参数
特化与重定义:
一个类模板和一个新类(或者两个类模板)
重定义本质是要么是实现了两个类模板,要么是一个类模板加上 一个新的类;
特化本质是只实现同一个类模板,特化的目的仅仅是考虑一些特殊的情况类模板应该如何工作
使用的时候没有统一的方式,要选择用类模板的种类或用新的类;
特化:
以统一的方式使用类模板和特化类;编译器自动优先选择特化类;能用特化就不要重定义;函数模板只支持类型参数完全特化:
#include
#include
using namespace std;
/* 以下是函数模板的特化实验 */
template
< typename T >
bool Equal(T a, T b)
{
cout << "bool Equal(T a, T b)" << endl;
return a == b;
}
/* 函数完全特化解决浮点数比较问题 */
template
< >
bool Equal(double a, double b)
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
/* 直接重载 */
bool Equal(double a, double b)
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
int main()
{
Test t3; // 这里错误了,要用 Test_Void t3; 这样的 定义方式,因为重定义了类的实现方式,注销了模板特化方式;写代码时,要时刻考虑究竟是要使用类模板 还是要使用新类,这就是弊端,所以能特化时,就不要重新定义、重新实现;
cout << Equal( 1, 1 ) << endl; // bool Equal(T a, T b) 1
cout << Equal<>( 0.001, 0.001 ) << endl; // 用相等符号比较两个浮点数是否相等是有问题的;用了特化后:bool Equal(double a, double b) 1
cout << Equal( 0.001, 0.001 ) << endl; // bool Equal(double a, double b) 1;这里调用全局重载函数,因为编译器会优先寻找全局重载函数;
return 0;
}
当需要重载函数模板时,优先考虑使用模板特化;当模板特化无法满足需求,再使用函数重载!
小结:
1.类模板可以定义任意多个不同的类型参数;
2.类模板可以被部分特化和完全特化;
3.特化的本质是模板的分开实现;
4.函数模板只支持完全特化;
5.工程中使用模板特化代替类(函数)重定义;
全特化与部分特化
template
class Test
{
public:
Test(T1 i,T2 j):a(i),b(j){cout<<"模板类"<
class Test
{
public:
Test(int i, char j):a(i),b(j){cout<<"全特化"<
class Test
{
public:
Test(char i, T2 j):a(i),b(j){cout<<"部分特化"< t1(0.1,0.2); //类模板
Test t2(1,'A');//全特化
Test t3('A',true);//部分特化
函数不存在部分特化的功能,可以通过函数的重载完成。
函数模板作为类模板成员
类模板中的成员函数还可以是一个函数模板。成员函数模板只有在被调用时才会被实例化。例如下面的程序:
#include
using namespace std;
template
class A
{
public:
template
void Func(T2 t) { cout << t; } //成员函数模板
};
int main()
{
A a;
a.Func('K'); //成员函数模板Func被实例化
a.Func("hello");
return 0;
}