vector常见接口的模拟实现
因为vector的很多接口与string的用法差不多而我已经写过string常见接口的用法了所以我这里只会简短的介绍一下vector和string某些接口的不同之处以及实现所有的常见接口。vector的所有接口接口一.了解vectorvector就是顺序表但与我们之前学的顺序表又有些不同。之前的顺序表的成员中我们有储存数据的指针、代表有效数据个数的size以及表示容量的capacity但在vector中储存数据、size、capacity用三个指针来表示_start,_finish,_end_of_storage。_start储存的第一个数据的指针_finish储存的最后一个数据的下一位置指针_end_of_storage最大容量的下一位置指针size_finish - _startcapacity _end_of_storage - _startvector是一个类模板每次使用它时都需要用类型显示实例化如vectorintv;vectorcharv1;vectorstringv2;vectorvector类型v3;二. vector某些接口介绍1.构造函数vectorintv1;vectorintv2(10,1);//vectorint v2(n,val)开辟10个vector大小每个空间储存的数据为1vectorintv3(v2);它的遍历也可以用迭代器for(autoi:v2){couti ;}2.容量控制类探究vector的扩容voidtest_vector2(){vectorintv;size_t szv.capacity();for(inti0;i100;i){v.push_back(i);if(sz!v.capacity()){coutcapacity to:;szv.capacity();coutszendl;}}//可见在vs环境下空间差不多1.5倍的增}与string有些区别的函数reserve():string的reserve在某些环境下可能可以达到缩容的作用但vector的绝对不会缩容。voidtest_vector3(){vectorintv(10,1);v.reserve(20);//10 20coutv.size() v.capacity()endl;v.reserve(5);coutv.capacity()endl;v.reserve(15);coutv.capacity()endl;v.reserve(30);coutv.capacity()endl;//可以发现不会缩小容量在其他平台也是//string在vs不会在其他环境可能会缩小但总比size大}3.插入与删除string的insert和erase接口不仅可以用下标的方式指定要插入或删除数据的位置而vector的只允许用迭代器voidtest_vector4(){vectorintv(10,1);v.insert(v.begin(),9);v.insert(v.begin()2,9);v.insert(v.begin()6,9);v.insert(v.begin()4,9);for(autoi:v){couti ;}//vector的insert只能用迭代器指定位置coutendl;v.erase(v.begin()3);//删除位置3的数据for(autoi:v){couti ;}coutendl;v.erase(v.begin()4,v.begin()5);//左闭右开相当于只删除位置4的for(autoi:v){couti ;}coutendl;//vector 的erase也是只能用迭代器}三.模拟实现vector1.准备阶段为了不与标准库里的vector冲突我们要自定义一个命名空间namespacemy{}因为vector也是用连续的空间储存顺序所以在这里我们也可以用指针模拟实现迭代器namespacemy{templateclassTclassvector{public:typedefT*iterator;typedefconstT*const_iterator;private:iterator _startnullptr;//第一个数据的坐标iterator _finishnullptr;//最后一个数据的坐标的下一个iterator _end_of_storagenullptr;//所开辟空间的下一位置};}2.一些简单函数的实现//获取有效数据个数size_tsize()const{return_finish-_start;}//获取容量size_tcapacity()const{return_end_of_storage-_start;}//获取开头元素的迭代器iteratorbegin(){return_start;}//获取末尾元素的迭代器iteratorend(){return_finish;}const_iteratorbegin()const{return_start;}const_iteratorend()const{return_finish;}//判空boolempty()const{return_start_finish;}//清理有效数据voidclear(){_finish_start;}//得以像数组一样用[]访问Toperator[](size_t n){assert(nsize());return_startn;}constToperator[](size_t n)const{assert(nsize());return_startn;}//析构~vector(){if(_start){delete[]_start;_start_finish_end_of_storagenullptr;}}3.预留空间reserve:voidreserve(size_t n)//n如果小于原来的空间就什么都不做{if(ncapacity()){size_t old_sizesize();T*tmpnewT[sizeof(T)*n];//memcpy(tmp, _start, sizeof(T) * size());for(size_t i0;iold_size;i){*(tmpi)*(_starti);}delete[]_start;//这里一释放三个成员变量都失效了所以要提前记入size_starttmp;_finish_startold_size;_end_of_storage_startn;}}这里千万不能用memcpy如果T是string或vector等类型拷贝的内容就是它们的指针而下一步是要释放原来的空间的resize:voidresize(size_t n,constTvalT())//这里T()是对传过来的类型调用该类型的构造函数初始化//为了满足这个条件c的内置类型也有自己的构造函数见test函数{assert(n0);if(nsize()){_finish_startn;}else{if(ncapacity())reserve(n);iterator end_finish;while(end!_startn){*(end)val;}_finish_startn;}}4.尾插与尾删voidpush_back(constTx)//这里传引用更好因为如果T是string之类的传值代价太大{if(size()capacity()){reserve(capacity()0?4:2*capacity());}*_finishx;_finish;}voidpop_back(){assert(_start!_finish);--_finish;}5.指定位置插入与删除与迭代器失效iteratorinsert(iterator pos,constTx){assert(pos_start);assert(pos_finish);//因为它们的迭代器是连续的才能用比较大小的写法if(size()capacity()){size_t old_pospos-_start;//开空间后指针都会变pos变野reserve(capacity()0?4:2*capacity());pos_startold_pos;}iterator end_finish;while(end!pos-1){*end*(end-1);end--;}*posx;_finish;returnpos;//返回修改后pos的迭代器}iteratorerase(iterator pos){assert(pos_start);assert(pos_finish);iterator endpos1;size_t old_pospos-_start;while(end!_finish){*(end-1)*end;end;}pos_startold_pos;_finish--;returnpos;}迭代器失效1.在insert函数中需要开辟空间的情况如果是这样if(size()capacity()){reserve(capacity()0?4:2*capacity());}程序会出错因为但我们开了新的空间旧的迭代器就会失效就像这函数中的pos开辟空间并把值转移后它依旧指向的是原来的数据如果继续访问它那么就会像访问野指针一样访问不属于我们的空间导致程序崩溃。此时该迭代器旧类似于野指针是不可以用的。这里就是迭代器失效的第一种情况2.在调用insert和erase函数期间vectorintv(9,1);autoposv.begin()2;autopos2v.end()-3;v.insert(pos,8);v.erase(pos2);//以下操作将不被允许*pos9;*pos2*2;//如果这样才可以posv.insert(pos,8);pos2v.erase(pos2);*pos9;*pos2*2;c规定使用了pos当插入或删除的参数传过去后如果没有更新将不允许继续使用pos进行任何操作否则会报错这是第二种迭代器失效的场景。6.复制重载与构造函数不带参的默认构造/*vector() { }*///也可以vector()default;//强转编译器生成默认构造//带参的构造(非拷贝)vector(size_t n,constTvalT()){reserve(n);for(size_t i0;in;i){push_back(val);}}这里的val依旧是传引用最好因为如果T是string或vector等类型传值消耗将会比较大。//拷贝构造vector(intn,constTvalT()){reserve(n);for(size_t i0;in;i){push_back(val);}}这里依然不能用memcpy//赋值重载iteratoroperator(constvectorTv){if(this!v)//防止自己给自己赋值{clear();reserve(v.capacity());for(autoval:v){push_back(val);}}}更简便的赋值重载voidswap(vectorTv){std::swap(_start,v._start);std::swap(_finish,v._finish);std::swap(_end_of_storage,v._end_of_storage);}iteratoroperator(vectorTv){swap(v);returnthis;}这里交换了临时变量和this成员变量的指针让原来的指针代替临时指针释放了。7.类模板里的函数模板在vector的构造函数中有一个是要用迭代器当参数构造的而库中的各种容器都能用迭代器也就是说可以用不同容器初始化vector不过数据类型要一致因此我们可以写一个函数模板//在类模板里还能继续写函数模板templateclassInputIterator//这样还能用char的vector复制stringvector(InputIterator begin,InputIterator end){while(begin!end){push_back(*begin);begin;}}比如我们可以用string初始化vectorvoidtest_vector6(){stringst(fshfshf);vectorcharv(st.begin(),st.end());}但如果我们写了这个函数模板程序就会出现一点问题vectorintv(3,7);像这句代码我们的目的得到一个有效数据为3个且都是7的vector但当它真的去调函数时你觉得它会调哪个结果是调函数模板为什么1.整形c会默认是int类型这里的37被识别为int2.如果要调用我们想要的那个就必须强转成size_t3.而函数模板的参数是两个相同类型3/7也都为int所以会优先选择调用起来偏简单的函数模板。那该如何解决该问题呢我们可以重载一个n为int而不是size_t的函数vector(intn,constTvalT()){reserve(n);for(size_t i0;in;i){push_back(val);}}这样传int就会优先匹配这个函数。四.完整代码分享gitee链接
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2469619.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!