在声明变量,函数,和大多数其他类型实体的时候,C++要求我们使用指定的类型。然而,有许多代码,除了类型不同之外,其余部分看起来都是相同的,比如,下面这个例子:

boolIsEqual(intleft,intright){returnleft==right;}boolIsEqual(conststring&left,conststring&right){returnleft==right;}voidtest(){strings1("s1"),s2("s2");cout<<IsEqual(s1,s2)<<endl;cout<<IsEqual(1,1)<<endl;}

上面这个例子,是为了比较两个变量是否相等的重载函数。这两个函数功能相同,只是处理的参数类型不同,那如果你需要处理float,double,等一系列类型时,你就要一一写出这些类型的重载函数,这样代码会显得十分繁琐,这时,就需要使用模板函数来处理了,模板函数只需要写一个就可以处理上面这种问题。

template<typenameT>boolIsEqual(constT&left,constT&right){returnleft==right;}voidtest1(){strings1("s1"),s2("s2");cout<<IsEqual(s1,s2)<<endl;cout<<IsEqual(1,1)<<endl;}

在编译模板函数时,编译器会根据传入的参数,自动推演出模板形参类型,并自动生成相应的代码,这样就相对于上面使用函数重载方式,代码量就大大减少,因为编译器会帮助你推演出相应代码。


当上面处理left,right类型不同时,使用模板函数时就需要作如下处理:

template<typenameT>boolIsEqual(constT&left,constT&right){returnleft==right;}voidtest2(){cout<<IsEqual(1,1)<<endl;//cout<<IsEqual(1,1.2)<<endl;//模板参数不匹配cout<<IsEqual<int>(1,1.2)<<endl;//显示实例化cout<<IsEqual<double>(1,1.2)<<endl;//显示实例化}

那么我们就知道使用模板函数就要注意模板参数的匹配问题,你也可以使用显示实例化方式,强制处理这种情况的发生。要是你就是想要比较两种不同类型,那就需要重载函数模板,使它可以接受两种类型,下面就是重载之后的:

boolIsEqual(constint&left,constint&right){returnleft==right;}template<typenameT>boolIsEqual(constT&left,constT&right){returnleft==right;}template<typenameT1,typenameT2>boolIsEqual(constT1&left,constT2&right){returnleft==right;}voidtest3(){cout<<IsEqual(1,1)<<endl;cout<<IsEqual<int>(1,1)<<endl;cout<<IsEqual(1,1.2)<<endl;}

模板类

/*模板类的格式*/template<classname1,classname2,...classnamen>classname{...};

以前在处理顺序表时,要更改其中的data类型,往往是通过修改typedef intDataType ,来修改其存放的数据类型,那么现在就可以使用模板类来不需要手动去修改其类型,下面是使用模板类实现顺序表:

#include<string>#include<cassert>usingnamespacestd;template<classT>classSeqList{public:SeqList():_data(NULL),_size(0),_capacity(0){CheakCapacity();}~SeqList(){if(_data!=NULL){delete[]_data;}}public:voidPushBack(constT&d){CheakCapacity();_data[_size]=d;_size++;}voidPushFront(constT&d){CheakCapacity();inti=_size;for(i;i>0;i--){_data[i]=_data[i-1];}_data[i]=d;_size++;}voidPopBack(){if(_size==0){cout<<"Listisempty!!"<<endl;return;}_size--;}voidPopFront(){inti=0;if(_size==0){cout<<"Listisempty!!"<<endl;return;}for(i;i<_size;i++){_data[i]=_data[i+1];}_size--;}public:voidCheakCapacity(){if(_size==_capacity){T*tmp=newT[_capacity+3];memcpy(tmp,_data,(_capacity)*sizeof(T));delete[]_data;_data=tmp;_capacity=_capacity+3;}}voidDisplay(){inti=0;for(i;i<_size;i++){cout<<_data[i]<<"";}cout<<"over"<<endl;}private:T*_data;int_size;int_capacity;};voidtest4(){SeqList<int>L;L.PushBack(1);L.PushBack(2);L.PushBack(3);L.PushBack(4);L.PushBack(5);L.Display();}intmain(){test4();system("pause");return0;}

结果:

当测试为下面test5()时:

voidtest5(){SeqList<string>L;L.PushBack("11111111111");L.PushBack("21111111111");L.PushBack("31111111111");L.PushBack("41111111111");L.PushBack("51111111111");L.PushBack("61111111111");L.Display();}

结果:

这为什么会崩溃呢?

因为使用memcpy()时:当我们拷贝的是基本类型时,只用拷贝所传递指针上的数据,如果是string类型呢,我们则需要在堆上开辟空间,所传递的指针如 果被直接复制,则有可能(vs下的string类型的实现原理是若字符串不长则以数组保存,若字符串过长,则通过指针在堆上开辟空间进行保存)出现同一地 址,析构两次这样的常见错误。

那么要解决上面的问题,就要使用c++中的类型萃取技术。

类型萃取是一种常用的编程技巧,其目的是实现不同类型数据面对同一函数实现不同的操作,它与类封装的区别是,我们并不用知道我 们所调用的对象是什么类型,类型萃取是编译后知道类型,先实现,而类的封装则是先定义类型,后实现方法。在这里我们可以用模板的特化实现其编程思想。

再来实现上面的顺序表:

#include<iostream>#include<string>#include<cassert>usingnamespacestd;template<classT>classSeqList{public:SeqList():_data(NULL),_size(0),_capacity(0){CheakCapacity();}~SeqList(){if(_data!=NULL){delete[]_data;}}public:voidPushBack(constT&d){CheakCapacity();_data[_size]=d;_size++;}voidPushFront(constT&d){CheakCapacity();inti=_size;for(i;i>0;i--){_data[i]=_data[i-1];}_data[i]=d;_size++;}voidPopBack(){if(_size==0){cout<<"Listisempty!!"<<endl;return;}_size--;}voidPopFront(){inti=0;if(_size==0){cout<<"Listisempty!!"<<endl;return;}for(i;i<_size;i++){_data[i]=_data[i+1];}_size--;}public:intFind(constT&d){inti=0;for(i;i<_size;i++){if(_data[i]==d){returni;}}return-1;}voidInsert(intpos,constT&d){CheakCapacity();inti=0;for(i=_size;i>pos;i--){_data[i]=_data[i-1];}_data[pos]=d;_size++;}voidErase(intpos){assert(pos>0);assert(pos<_size);inti=pos;for(i;i<_size;i++){_data[i]=_data[i+1];}_size--;}voidSort(){inti,j;for(i=0;i<_size;i++){for(j=0;j<_size-1-i;j++){if(_data[j]>_data[j+1]){Ttmp=_data[j];_data[j]=_data[j+1];_data[j+1]=tmp;}}}}public:voidCheakCapacity(){if(_size==_capacity){T*tmp=newT[_capacity+3];if(TypeTraits<T>::isPODType().Get()){memcpy(tmp,_data,(_capacity)*sizeof(T));}else{for(inti=0;i<_size;i++){tmp[i]=_data[i];}}delete[]_data;_data=tmp;_capacity=_capacity+3;}}voidDisplay(){inti=0;for(i;i<_size;i++){cout<<_data[i]<<"";}cout<<"over"<<endl;}private:T*_data;int_size;int_capacity;};structFalseType{boolGet(){returnfalse;}};structTrueType{boolGet(){returntrue;}};template<classT>structTypeTraits{typedefFalseTypeisPODType;//内嵌型别};structTypeTraits<char>{typedefTrueTypeisPODType;//内嵌型别};template<>structTypeTraits<int>{typedefTrueTypeisPODType;//内嵌型别};/*还有许多基本类型没有显示写出来,bool,float,double...*/voidtest6(){SeqList<string>L;L.PushBack("11111111111");L.PushBack("21111111111");L.PushBack("31111111111");L.PushBack("41111111111");L.PushBack("51111111111");L.PushBack("61111111111");L.Display();}intmain(){test6();system("pause");return0;}

结果:


模板总结

优点:

1. 模板复用了代码,节省资源,更快的迭×××发,C++的标准模板库(STL)因此而产生。

2. 增强了代码的灵活性。

缺点:

1. 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。

2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。

类型萃取总结:

类型萃取技术可以大大加快代码的效率,也可以让思路变得更清晰。

要是上面在拷贝时,其实不用memcpy()也可以,只要将对象一个一个的拷贝,也是可行的,但程序的效率就会大大降低。

在调试的时候会让本人思路更加清晰。