模板实现动态顺序表(含容量检测函数的详细讲解)
2017-07-11 01:30
561 查看
一、模板实现动态顺序表
(1)要实现的成员函数和成员变量
(2)模板实现时由于要适用于所有的类型,所以在写容量检测函数时要注意
下面详细讲一下容量检测函数不同写法对某些类型的不适用;
(2.1)方法一:用realloc()开辟空间;
realloc开辟空间,是在原来空间的基础上扩大空间;返回开辟完成的空间地址;即就是原来的空间的地址;或者如果原来空间的后面没有多余的空间开辟,则realloc重新开辟一款大小正确的空间;然后将原来的空间数据拷贝到全新的空间中,将原来的空间释放;将新的空间的首地址返回;所有我们在使用realloc时,不能直接用原来的空间的指针接收realloc的返回值;因为,如果一旦在原来的空间基础上没有开辟成功,又没有多余的空间开辟全新的空间大小,那么realloc会返回NULL;这样一旦用原先的地址指针接收,会将原来的空间丢失;以前空间的数据也会找不到;所以如果用realloc开辟空间;必须建立一个临时变量就收reallo的返回值;然后进行返回值的检查,再决定是否赋给原先的指针;但是,用relloc有一个缺点,就是开辟的空间,不能初始化,这样的话;会在存储某些类型的数据时造成错误的输出;
(2.2)使用new操作符开辟空间;
使用new操作符进行扩容时,当顺序表中存储string类的字符串时,并且当字符串长度大于16字节时,在扩容并同时拷贝元顺序表的内容是,会出现浅拷贝的问题;是的顺序表数据丢失;
我们知道string类对象的对象模型为
string类的对象的大小为32字节;
在其对象模型中,如果string字符串的长度小于16,那么这些字符存放在string
对象中的_buf的字符数组中;但是,如果字符串的长度大于16,那么系统会在内存中开辟一块空间,存放字符串,并且把string中的_Ptr指针指向存放字符串的空间;
我上面的写法当执行到
那么此时新开辟的空间中对象的_Ptr所指向的存放长字符串的空间不见了;所以此时数据丢失;造成错误;
看一个例子:
结果:
图说:
(2.3)采用for循环挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题;会为开辟一段新的内存储存长字符串
- 结果:
- 测试结果:
END!!!!
(1)要实现的成员函数和成员变量
template<typename T> class SeqList { public: SeqList();//构造 SeqList(const T& seqlist);//拷贝构造 SeqList& operator=(const T& seqlist);//赋值运算符重载 ~SeqList();//析构 void PushBack(const T& d);//尾插 void PopBack();//尾删 void PushFront(const T& d);//头插 void PopFront();//头删 void Sort();//排序 void Remove(const T& x);//删除第一个出现的特定元素 void RemoveAll(const T& x);//删除所有出现的特定元素 void Earse(int index);//删除特定位置的元素 void Insert(int index,const T& x);//指定的位置插入某个元素 void Reserve(int sz);//增容到指定的大小 void Display();//打印顺序表 private: void CheakCapacity();//容量检测函数 private: T* _pdata; int _sz; int _capacity;
(2)模板实现时由于要适用于所有的类型,所以在写容量检测函数时要注意
下面详细讲一下容量检测函数不同写法对某些类型的不适用;
(2.1)方法一:用realloc()开辟空间;
realloc开辟空间,是在原来空间的基础上扩大空间;返回开辟完成的空间地址;即就是原来的空间的地址;或者如果原来空间的后面没有多余的空间开辟,则realloc重新开辟一款大小正确的空间;然后将原来的空间数据拷贝到全新的空间中,将原来的空间释放;将新的空间的首地址返回;所有我们在使用realloc时,不能直接用原来的空间的指针接收realloc的返回值;因为,如果一旦在原来的空间基础上没有开辟成功,又没有多余的空间开辟全新的空间大小,那么realloc会返回NULL;这样一旦用原先的地址指针接收,会将原来的空间丢失;以前空间的数据也会找不到;所以如果用realloc开辟空间;必须建立一个临时变量就收reallo的返回值;然后进行返回值的检查,再决定是否赋给原先的指针;但是,用relloc有一个缺点,就是开辟的空间,不能初始化,这样的话;会在存储某些类型的数据时造成错误的输出;
void CheakCapacity() { if (_capacity==_sz) { int NewCapacity=_capacity*2+3; T* tmp=(T*)realloc(_pdata,NewCapacity); if (tmp==NULL) { perror("realloc"); exit(EXIT_FAILURE); } else { _pdata=tmp; _capacity=NewCapacity; } } }
(2.2)使用new操作符开辟空间;
void CheakCapacity() { if (_capacity==_sz) { int NewCapacity=_capacity*2+3; T* tmp=new T[NewCapacity]; memcpy(tmp,_pdata,sizeof(T)*_sz); delete[] _pdata; _pdata=tmp; _capacity=NewCapacity; } }
使用new操作符进行扩容时,当顺序表中存储string类的字符串时,并且当字符串长度大于16字节时,在扩容并同时拷贝元顺序表的内容是,会出现浅拷贝的问题;是的顺序表数据丢失;
我们知道string类对象的对象模型为
string类的对象的大小为32字节;
在其对象模型中,如果string字符串的长度小于16,那么这些字符存放在string
对象中的_buf的字符数组中;但是,如果字符串的长度大于16,那么系统会在内存中开辟一块空间,存放字符串,并且把string中的_Ptr指针指向存放字符串的空间;
我上面的写法当执行到
memcpy(tmp,_pdata,sizeof(T)*_sz);这句的时候,由于memcpy();是一个内存拷贝函数;在拷贝存放大于16字节的字符串事只是把_Ptr指针拷贝到了新开的空间;然后接下来执行
delete[] _pdata;这句的时候,会调用析构函数,析构函数会先把以前的空间的对象清理,在清理的过程中会把空间中指针所指向的保留长字符串的内存空间释放;然后再把该对象空间释放;
那么此时新开辟的空间中对象的_Ptr所指向的存放长字符串的空间不见了;所以此时数据丢失;造成错误;
看一个例子:
#include<iostream> #include<string> using namespace std; template<typename T> class SeqList { public: SeqList();//构造 ~SeqList();//析构 void PushBack(const T& d);//尾插 void Display(); private: void CheakCapacity() { if (_capacity==_sz) { int NewCapacity=_capacity*2+3; T* tmp=new T[NewCapacity]; memcpy(tmp,_pdata,sizeof(T)*_sz); delete[] _pdata; _pdata=tmp; _capacity=NewCapacity; } } private: T* _pdata; int _sz; int _capacity; }; template<typename T> SeqList<T>::SeqList()//构造 :_pdata(NULL) ,_sz(0) ,_capacity(0) {} template<typename T> SeqList<T>::~SeqList()//析构 { if (_pdata!=NULL) { delete[] _pdata; } _sz=0; _capacity=0; } template<typename T> void SeqList<T>::PushBack(const T& d)//尾插 { CheakCapacity();//检测容量 _pdata[_sz]=d; _sz++; } template<typename T> void SeqList<T>::Display() { for (int i=0;i<_sz;i++) { cout<<_pdata[i]<<endl; } } void test() { SeqList<string> s1; s1.PushBack("abcdef"); s1.PushBack("aaaaaa"); s1.PushBack("rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"); s1.PushBack("bbbbbb"); s1.PushBack("eeeeeee"); s1.Display(); } int main() { test(); return 0; }
结果:
图说:
(2.3)采用for循环挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题;会为开辟一段新的内存储存长字符串
//方法三: void CheakCapacity() { if (_capacity==_sz) { int NewCapacity=_capacity*2+3; T* tmp=new T[NewCapacity]; //挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题; //会为开辟一段新的内存储存长字符串 for (int i=0;i<_sz;i++) { tmp[i]=_pdata[i]; } delete[] _pdata; _pdata=tmp; _capacity=NewCapacity; } }
- 结果:
二、完整实现
#include<iostream> #include<string> #include<assert.h> using namespace std; template<typename T> class SeqList { public: SeqList();//构造 ~SeqList();//析构 void PushBack(const T& d);//尾插 void PopBack();//尾删 void PushFront(const T& d);//头插 void PopFront();//头删 void Sort();//排序 void Remove(const T& d);//删除第一个出现的特定元素 void RemoveAll(const T& d);//删除所有出现的特定元素 void Earse(int index);//删除特定位置的元素 void Insert(int index,const T& d);//指定的位置插入某个元素 void Reserve(int sz);//增容到指定的大小 T& operator[](const int index);//重载[] void Display();//打印 void PrintfCapacity();//打印容量 private: void CheakCapacity() { if (_capacity<=_sz) { int NewCapacity=_capacity*2+3; T* tmp=new T[NewCapacity]; //挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题; //会为开辟一段新的内存储存长字符串 for (int i=0;i<_sz;i++) { tmp[i]=_pdata[i]; } delete[] _pdata; _pdata=tmp; _capacity=NewCapacity; } } void CheakCapacity1(int sz) { if (_capacity<=sz) { int NewCapacity=sz; T* tmp=new T[NewCapacity]; //挨个赋值,string类型在赋值运算符重载函数中会解决浅拷贝问题; //会为开辟一段新的内存储存长字符串 for (int i=0;i<_sz;i++) { tmp[i]=_pdata[i]; } delete[] _pdata; _pdata=tmp; _capacity=NewCapacity; } } private: T* _pdata; int _sz; int _capacity; }; template<typename T> SeqList<T>::SeqList()//构造 :_pdata(NULL) ,_sz(0) ,_capacity(0) {} template<typename T> SeqList<T>::~SeqList()//析构 { if (_pdata!=NULL) { delete[] _pdata; } _sz=0; _capacity=0; } template<typename T> void SeqList<T>::PushBack(const T& d)//尾插 { CheakCapacity();//检测容量 _pdata[_sz]=d; _sz++; } template<typename T> void SeqList<T>::PopBack()//尾删 { _sz--; } template<typename T> void SeqList<T>::PushFront(const T& d)//头插 { CheakCapacity(); for (int i=_sz;i>=1;i--) { _pdata[i]=_pdata[i-1]; } _pdata[0]=d; _sz++; } template<typename T> void SeqList<T>::PopFront()//头删 { for (int i=0;i<_sz-1;i++) { _pdata[i]=_pdata[i+1]; } _sz--; } template<typename T> void SeqList<T>::Sort()//排序 { if (_pdata==NULL) { printf("seqlist empty!"); } int mark=0; for(int i=0;i<_sz-1;i++)//比较趟数 { for (int j=0;j<_sz-i-1;j++) { if (_pdata[j]>_pdata[j+1]) { mark=1; T tmp=_pdata[j]; _pdata[j]=_pdata[j+1]; _pdata[j+1]=tmp; } } if (mark=0) { break; } } } template<typename T> void SeqList<T>::Remove(const T& d)//删除第一个出现的特定元素 { if (_pdata==NULL) { printf("seqlist empty!"); return; } int pos=0; while(pos<_sz&&_pdata[pos++]!=d)//找元素 {} pos-=1;//该元素的位置,一定要注意pos-1; if (_pdata[pos]==d) { if (pos==_sz-1)//处理元素为最后一个 { PopBack(); } for(int i=pos;i<_sz-1;i++)//不是最后一个 { _pdata[i]=_pdata[i+1]; } _sz--; } } template<typename T> void SeqList<T>::RemoveAll(const T& d)//删除所有出现的特定元素 { if (_pdata==NULL) { printf("seqlist empty!"); } int pos=0; while (pos<_sz+1) { while(pos<=_sz&&_pdata[pos++]!=d)//找元素 {} pos-=1;//该元素的位置 if (_pdata[pos]==d) { if (pos==_sz-1)//处理元素为最后一个 { PopBack(); } for(int i=pos;i<_sz-1;i++) { _pdata[i]=_pdata[i+1]; } _sz--; } if (pos==_sz) { break; } } } template<typename T> void SeqList<T>::Earse(int index)//删除特定位置的元素 { if (index<0||index>=_sz) { return; } if (index==_sz) { PopBack(); } else { for (int i=index;i<index-1;i++) { _pdata[i]=_pdata[i+1]; } _sz--; } } template<typename T> void SeqList<T>::Insert(int pos,const T& x)//指定的位置之前插入某个元素 { CheakCapacity(); if (pos<0||pos>_sz) { return ; } for (int i=_sz;i>pos;i--) { _pdata[i]=_pdata[i-1]; } _pdata[pos]=x; _sz++; } template<typename T> void SeqList<T>::Reserve(int sz)//增容到指定的大小 { CheakCapacity1(sz); } template<typename T> T& SeqList<T>::operator[](const int index)//重载[] { assert(index>=0&&index<_sz); return _pdata[index]; } template<typename T> void SeqList<T>::Display()//打印 { for (int i=0;i<_sz;i++) { cout<<_pdata[i]<<" "; } cout<<endl; } template<typename T> void SeqList<T>::PrintfCapacity() { cout<<_capacity<<endl; } void test() { SeqList<int> s; s.PushBack(1); s.PushBack(2); s.PushBack(3); s.PushBack(4); s.PushBack(3); s.PushBack(5); s.PushBack(3); s.PushFront(10); s.PushFront(3); s.PushFront(11); s.PushFront(3); s.PushFront(12); cout<<"尾插头插以后的原始顺序表"<<endl; s.Display(); s.PopBack(); s.PopFront(); cout<<"尾删头删以后的顺序表"<<endl; s.Display(); cout<<"删除第一个出现的3"<<endl; s.Remove(3); s.Display(); cout<<"删除所有出现的3"<<endl; s.RemoveAll(3); s.Display(); cout<<"删除特定5元素"<<endl; s.Earse(5); s.Display(); cout<<"位置2插入元素100"<<endl; s.Insert(2,100); s.Display(); cout<<"重载[],答应第4个元素"<<endl; cout<<s[4]<<endl; cout<<"没使用Reserve()增容之前链表的容量->"; s.PrintfCapacity(); s.Reserve(100); cout<<"增容到指定100打印容量"<<endl; s.Reserve(100); s.PrintfCapacity(); cout<<"最后顺序表的元素"<<endl; s.Display(); cout<<"排序"<<endl; s.Sort(); s.Display(); } void test1() { SeqList<string> s; s.PushBack("a"); s.PushBack("b"); s.PushBack("c"); s.PushBack("d"); s.PushBack("a"); s.PushBack("e"); s.PushBack("f"); s.PushBack("a"); s.PushFront("r"); s.PushFront("ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"); s.PushFront("o"); s.PushFront("a"); cout<<"尾插头插以后的原始顺序表"<<endl; s.Display(); s.PopBack(); s.PopFront(); cout<<"尾删头删以后的顺序表"<<endl; s.Display(); cout<<"删除第一个出现的a"<<endl; s.Remove("a"); s.Display(); cout<<"删除所有出现的a"<<endl; s.RemoveAll("a"); s.Display(); cout<<"删除特定位置5的元素"<<endl; s.Earse(5); s.Display(); cout<<"位置2插入元素helle world"<<endl; s.Insert(2,"helle world"); s.Display(); cout<<"重载[],打印第4个元素"<<endl; cout<<s[4]<<endl; cout<<"没使用Reserve()增容之前链表的容量->"; s.PrintfCapacity(); s.Reserve(100); cout<<"增容到指定100打印容量"<<endl; s.Reserve(100); s.PrintfCapacity(); cout<<"最后顺序表的元素"<<endl; s.Display(); cout<<"排序"<<endl; s.Sort(); s.Display(); } int main() { test(); cout<<endl; test1(); return 0; }
- 测试结果:
END!!!!
相关文章推荐
- 利用C++模板,代替虚函数实现类的静态多态性及动态继承
- 模板实现动态顺序表
- [转]详细讲解检测文件编码的实现方法
- 利用C++模板,代替虚函数实现类的静态多态性及动态继承
- C++模板实现动态顺序表(更深层次的深浅拷贝)与基于顺序表的简单栈的实现
- 利用C++模板,代替虚函数实现类的静态多态性及动态继承
- 利用动态加载模板,配合ajax实现无刷新操作
- 使用struts-menu_详细实现动态菜单
- DWR+freemarker+commons.mail 实现模板定制动态邮件发送 推荐
- asp代码实现检测组件是否安装的函数
- 地形渲染的动态LOD四叉树算法详细实现
- 使用struts-menu_详细(1)实现动态菜单
- asp 实现检测字符串是否为纯字母和数字组合的函数
- 初学STRUTS---实现注册/登录/动态检测注册信息
- 使用"函数递归"实现基于php和MySQL的动态树型菜单
- ajax1.0用户动态检测注册实现(原创)
- 利用动态加载模板,配合ajax实现无刷新操作
- 使用函数递归实现基于php和MySQL的动态树型菜单
- dll函数动态调用工具1.0(Delphi+asm实现)
- asp动态include文件,方便多模板的实现