C++模板类的类型萃取技术

来源:互联网 发布:js二维数组固定长度 编辑:程序博客网 时间:2024/06/06 12:38

模板是泛型编程的基础,所谓泛型编程就是指编写与类型无关的逻辑代码,是一种复用的方式。所以使用模板的目的就是方便程序员编写与类型无关的代码,减少相似代码在程序中出现的概率。

假如我们要编写一个判断两个变量是否相等的函数,那么为了处理int,char,double等不同类型的变量,我们得一一写出这些类型的重载函数。这样不仅代码会显得十分冗余,也会增加程序员无谓的工作量。为了解决这个问题,C++中引入了模板的概念。如下,我们可以利用模板来编写判断两个变量是否相等的函数。

template<class T>bool IsEqual(const T& left, const T& right){return left == right;}

void Test1(){string s1("xiaoxu");string s2("xiaoyu");cout << IsEqual(s1, s2) << endl;  //比较string类型的变量cout << IsEqual(11, 11) << endl;  //比较int类型的变量cout << IsEqual('1', '1') << endl;//比较char类型的变量}

有了模板,我们不仅可以编写模板函数还可以编写模板类。以下就是利用模板实现顺序表的代码。

template<typename T>class SeqList{public:SeqList():_array(NULL), _size(0), _capacity(0){}SeqList<T>(const SeqList<T>& s):_array(new T[s._size]), _size(s._size), _capacity(s._size){memcpy(_array, s._array, sizeof(T)*s._size);}/*SeqList<T>& operator=(const SeqList<T>& s){if (this != &s){T* tmp = new T[s._size];memcpy(tmp, s._array, sizeof(T)*s._size);delete[] _array;_array = tmp;_size = s._size;_capacity = s._size;}return *this;}*/SeqList<T>& operator=(const SeqList<T>& s){if (this != &s){SeqList tmp(s);swap(_array, tmp._array);swap(_size, tmp._size);swap(_capacity, tmp._capacity);}return *this;}/*SeqList<T>& operator=(SeqList<T> s){swap(_array, s._array);swap(_size, s._size);swap(_capacity, s._capacity);return *this;}*/~SeqList(){if (_array){delete[] _array;_size = _capacity = 0;}}void PushBack(const T& d);void PopBack();void PushFront(const T& d);void PopFront();void Print();void Insert(size_t pos, const T& d);void Erase(size_t pos);size_t Find(const T& d);const T& Back();size_t Size();bool Empty();protected:void CheckCapacity(){if (_size == _capacity){_capacity = _capacity * 2 + 3;_a = (DataType*)realloc(_a, sizeof(DataType)*_capacity);}}protected:T* _array;size_t _size;size_t _capacity;};template<typename T>void SeqList<T>::PushBack(const T& d){_CheckCapacity();_array[_size] = d;_size++;}template<typename T>void SeqList<T>::PopBack(){if (_size > 0){_size--;}else{cout << "顺序表为空,无法尾删" << endl;}}template<typename T>void SeqList<T>::PushFront(const T& d){_CheckCapacity();for (size_t i = _size; i > 0; i--){_array[i] = _array[i-1];}_size++;_array[0] = d;}template<typename T>void SeqList<T>::PopFront(){if (_size > 0){for (size_t i = 0; i < _size; i++){_array[i] = _array[i + 1];}_size--;}else{cout << "顺序表为空,无法头删" << endl;}}template<typename T>void SeqList<T>::Insert(size_t pos, const T& d){_CheckCapacity();if (_size == 0){PushFront(d);}else{for (size_t i = _size; i > pos - 1; i--){_array[i] = _array[i - 1];}_array[pos - 1] = d;_size++;}}template<typename T>void SeqList<T>::Erase(size_t pos){if (_size > 0){for (size_t i = pos-1; i < _size; i++){_array[i] = _array[i + 1];}_size--;}else{cout << "顺序表为空,无法删除数据" << endl;}}template<typename T>size_t SeqList<T>::Find(const T& d){for (size_t i = 0; i < _size; i++){if (_array[i] == d){return i + 1;}}return 0;}template<typename T>void SeqList<T>::Print(){{if (_size > 0){for (size_t i = 0; i < _size; i++){cout << _array[i] << " ";}cout << endl;}else{cout << "顺序表为空" << endl;}}}

由于模板不支持分离编译,所以我将类的成员函数的声明和定义写在了一个.hpp文件中。

以下为测试代码。

void Test1(){SeqList<int> s1;s1.PushBack(1);s1.PushBack(2);s1.PushBack(3);s1.PushBack(4);s1.Print();//s1.PopBack();//s1.Print();//s1.PopBack();//s1.PopBack();//s1.PopBack();//s1.Print();//s1.PopBack();//s1.PushFront(4);//s1.PushFront(3);//s1.PushFront(2);//s1.PushFront(1);//s1.Print();//s1.PopFront();//s1.Print();//s1.PopFront();//s1.PopFront();//s1.PopFront();//s1.Print();//s1.PopFront();}
输出结果:


void Test2(){SeqList<string> s2;s2.PushBack("22");s2.PushBack("1111111111111111111111111111111111111111111111111111111111111111");s2.PushBack("33");s2.PushBack("44");s2.Print();//s2.PopBack();//s2.Print();//s2.PopBack();//s2.PopBack();//s2.PopBack(); //s2.Print();//s2.PopBack();}
输出结果:



可以看到,测试int类型时程序正常输出,测试string类型时程序却崩溃了。罪魁祸首是下面这条语句

memcpy(_array, s._array, sizeof(T)*s._size);

memcpy()是值拷贝函数,当我们使用它拷贝基本类型时没有任何问题,可是拷贝string类型时问题就出现了,VS下的string类型的实现原理是若字符串不长则以数组保存,若字符串过长(超过16),则通过指针在堆上开辟空间保存,所以当使用memcpy()函数拷贝较长的字符串(长度超过16)时,会直接拷贝指针,从而出现深浅拷贝的问题。

那么为了解决这个问题我们还得重新写一份代码吗?完全不用!用类型萃取技术即可解决。

类型萃取是一种常用的编程技巧,它可以使不同类型数据实现同一份函数代码。在调用时,事先并不知道对象是什么类型,编译器是开始编译后才进行模板推演,根据不同的对象类型生成相应的代码。以下就是用类型萃取优化上述代码。

struct __TrueType{bool Get(){return true;}};struct __FalseType{bool Get(){return false;}};template<typename T>struct TypeTraits  //类型萃取{typedef __FalseType IsPodType;  //Pod——内置类型};template<>struct TypeTraits<int>  //特化内置类型{typedef __TrueType IsPodType;};
重新改写一下_Checkcapacity()函数即可,分成两种实现方式可提高代码运行效率。
void _CheckCapacity(){if (_size >= _capacity){_capacity = _capacity * 2 + 3;if (TypeTraits<T>::IsPodType().Get())  //内置类型的实现方式{cout << "Pod Type" << endl;_array = (T*)realloc(_array, sizeof(T)*_capacity);}else   //非内置类型的实现方式{cout << "Not Pod Type" << endl;T* tmp = new T[_capacity];if (_array){//memcpy(tmp, _array, sizeof(T)*_size);for (size_t i = 0; i < _size; i++){tmp[i] = _array[i];}delete[] _array;}_array = tmp;}}}
成功输出string类型的顺序表:




0 0
原创粉丝点击