在声明变量,函数,和大多数其他类型实体的时候,C++要求我们使用指定的类型。然而,有许多代码,除了类型不同之外,其余部分看起来都是相同的,比如,下面这个例子:
bool IsEqual (int left, int right) { return left == right; } bool IsEqual (const string& left , const string& right) { return left == right; } void test() { string s1 ("s1"), s2("s2"); cout<<IsEqual (s1, s2)<<endl; cout<<IsEqual (1,1)<<endl; }
上面这个例子,是为了比较两个变量是否相等的重载函数。这两个函数功能相同,只是处理的参数类型不同,那如果你需要处理float,double,等一系列类型时,你就要一一写出这些类型的重载函数,这样代码会显得十分繁琐,这时,就需要使用模板函数来处理了,模板函数只需要写一个就可以处理上面这种问题。
template<typename T> bool IsEqual (const T& left , const T& right ) { return left == right; } void test1 () { string s1 ("s1"), s2("s2" ); cout<<IsEqual (s1, s2)<<endl ; cout<<IsEqual (1,1)<<endl; }
在编译模板函数时,编译器会根据传入的参数,自动推演出模板形参类型,并自动生成相应的代码,这样就相对于上面使用函数重载方式,代码量就大大减少,因为编译器会帮助你推演出相应代码。
当上面处理left,right类型不同时,使用模板函数时就需要作如下处理:
template <typename T> bool IsEqual (const T& left , const T& right ) { return left == right; } void test2() { 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; // 显示实例化 }
那么我们就知道使用模板函数就要注意模板参数的匹配问题,你也可以使用显示实例化方式,强制处理这种情况的发生。要是你就是想要比较两种不同类型,那就需要重载函数模板,使它可以接受两种类型,下面就是重载之后的:
bool IsEqual (const int& left , const int& right) { return left == right; } template <typename T> bool IsEqual (const T& left , const T& right ) { return left == right; } template <typename T1, typename T2> bool IsEqual (const T1& left , const T2& right) { return left == right; } void test3() { cout<<IsEqual(1,1)<<endl; cout<<IsEqual<int>(1,1)<< endl; cout<<IsEqual(1,1.2)<<endl; }
模板类
/*模板类的格式*/ template<class name1, class name2, ...class namen> class name { ... };
以前在处理顺序表时,要更改其中的data类型,往往是通过修改typedef int DataType ,来修改其存放的数据类型,那么现在就可以使用模板类来不需要手动去修改其类型,下面是使用模板类实现顺序表:
#include <string> #include <cassert> using namespace std; template <class T> class SeqList { public: SeqList() :_data(NULL) ,_size(0) ,_capacity(0) { CheakCapacity(); } ~SeqList() { if(_data != NULL) { delete[] _data; } } public: void PushBack(const T& d) { CheakCapacity(); _data[_size] = d; _size++; } void PushFront(const T& d) { CheakCapacity(); int i = _size; for(i; i>0; i--) { _data[i] = _data[i-1]; } _data[i] = d; _size++; } void PopBack() { if(_size == 0) { cout<<"List is empty!!"<<endl; return; } _size--; } void PopFront() { int i = 0; if(_size == 0) { cout<<"List is empty!!"<<endl; return; } for(i; i<_size; i++) { _data[i] = _data[i+1]; } _size--; } public: void CheakCapacity() { if(_size == _capacity) { T* tmp = new T[_capacity+3]; memcpy(tmp, _data, (_capacity)*sizeof(T)); delete[] _data; _data = tmp; _capacity = _capacity+3; } } void Display() { int i = 0; for(i; i<_size; i++) { cout<<_data[i]<<" "; } cout<<"over"<<endl; } private: T* _data; int _size; int _capacity; }; void test4() { SeqList<int> L; L.PushBack(1); L.PushBack(2); L.PushBack(3); L.PushBack(4); L.PushBack(5); L.Display(); } int main() { test4(); system("pause"); return 0; }
结果:
当测试为下面test5()时:
void test5() { 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> using namespace std; template <class T> class SeqList { public: SeqList() :_data(NULL) ,_size(0) ,_capacity(0) { CheakCapacity(); } ~SeqList() { if(_data != NULL) { delete[] _data; } } public: void PushBack(const T& d) { CheakCapacity(); _data[_size] = d; _size++; } void PushFront(const T& d) { CheakCapacity(); int i = _size; for(i; i>0; i--) { _data[i] = _data[i-1]; } _data[i] = d; _size++; } void PopBack() { if(_size == 0) { cout<<"List is empty!!"<<endl; return; } _size--; } void PopFront() { int i = 0; if(_size == 0) { cout<<"List is empty!!"<<endl; return; } for(i; i<_size; i++) { _data[i] = _data[i+1]; } _size--; } public: int Find(const T& d) { int i = 0; for(i; i<_size; i++) { if(_data[i] == d) { return i; } } return -1; } void Insert(int pos, const T& d) { CheakCapacity(); int i = 0; for(i=_size; i>pos; i--) { _data[i] = _data[i-1]; } _data[pos] = d; _size++; } void Erase(int pos) { assert(pos>0); assert(pos<_size); int i = pos; for(i; i<_size; i++) { _data[i] = _data[i+1]; } _size--; } void Sort() { int i,j; for(i=0; i<_size; i++) { for(j=0; j<_size-1-i; j++) { if(_data[j]>_data[j+1]) { T tmp = _data[j]; _data[j] = _data[j+1]; _data[j+1] = tmp; } } } } public: void CheakCapacity() { if(_size == _capacity) { T* tmp = new T[_capacity+3]; if(TypeTraits<T>::isPODType().Get()) { memcpy(tmp, _data, (_capacity)*sizeof(T)); } else { for(int i=0; i<_size; i++) { tmp[i] = _data[i]; } } delete[] _data; _data = tmp; _capacity = _capacity+3; } } void Display() { int i = 0; for(i; i<_size; i++) { cout<<_data[i]<<" "; } cout<<"over"<<endl; } private: T* _data; int _size; int _capacity; }; struct FalseType { bool Get() { return false; } }; struct TrueType { bool Get() { return true; } }; template <class T> struct TypeTraits { typedef FalseType isPODType;//内嵌型别 }; struct TypeTraits<char> { typedef TrueType isPODType;//内嵌型别 }; template<> struct TypeTraits<int> { typedef TrueType isPODType;//内嵌型别 };/*还有许多基本类型没有显示写出来,bool,float,double...*/ void test6() { SeqList<string> L; L.PushBack("11111111111"); L.PushBack("21111111111"); L.PushBack("31111111111"); L.PushBack("41111111111"); L.PushBack("51111111111"); L.PushBack("61111111111"); L.Display(); } int main() { test6(); system("pause"); return 0; }
结果:
模板总结
优点:
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2. 增强了代码的灵活性。
缺点:
1. 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
类型萃取总结:
类型萃取技术可以大大加快代码的效率,也可以让思路变得更清晰。
要是上面在拷贝时,其实不用memcpy()也可以,只要将对象一个一个的拷贝,也是可行的,但程序的效率就会大大降低。
在调试的时候会让本人思路更加清晰。