电子商务电商网站饿建设,房地产app开发,中国进出口贸易网官网,手机端wordpress模板下载引言#xff1a;
北京时间#xff1a;2023/3/9/12:58#xff0c;下午两点有课#xff0c;现在先把引言给搞定#xff0c;这样就能激励我更早的把这篇博客给写完了#xff0c;万事开头难这句话还是很有道理的#xff0c;刚好利用现在昏昏欲睡的时候#xff0c;把这个没…引言
北京时间2023/3/9/12:58下午两点有课现在先把引言给搞定这样就能激励我更早的把这篇博客给写完了万事开头难这句话还是很有道理的刚好利用现在昏昏欲睡的时候把这个没什么干货的引言写完并且刚刚刷了一下知乎我发现现在的知乎有两个字很合适形容掘金例我刚刚随意看了一个有关大学生4年应该干什么的文章写这种文章的我不多做评价我深信人无完人这一理念我深知自己并不优秀但我知道我的三观还行例如我并不觉得送外卖……可能有人说……我反而时常会想象自己熬夜送外卖的场景我觉得这样还挺好的凭自己的双手赚钱什么叫上好大学……什么叫高考……什么叫富二代……我只想好好看电视好好吃饭找一个能聊的来的朋友所以像什么吹牛比自卑好我不能理解反正我宁愿自卑我都不愿意吹牛做人不是为了给别人增加压力开玩笑的吹牛叫搞笑认真的吹牛叫说谎所以咱还是继续自卑吧做不到的事情、未可知的事情咱不干咱只谈实际咱做好自己例美剧……ok这篇博客我们就继续深入学习迭代器失效的问题和vector类知识的收尾然后把list带头双向循环链表给开个头下篇博客就是自我实现listSogogogo
前提此时的我因为上午写完了一篇博客现在心情非常的好只是有点困并且以我的情商舍友之间关系还是非常的ok的只是有点可惜没能找到能谈心的校园人际关系也还行毕竟我没有什么认识的人哈哈哈上述只是对知乎上的某些清华级别文章做出的感想并且通过文字证明耍嘴我也行哈哈哈好在这里没有吹牛写耍嘴我才是祖师爷哈哈哈其实我也只会哈哈哈别的我也不是很在行哈哈哈看到这么多哈哈哈这里刚好想起来明天5哈综艺就更新了哦又不会无聊啦不过谈到5哈这里我对当今的综艺情况也是略有一点见解的这里就不多做赘述了此处省略一万字……
再谈迭代器失效问题
上篇博客我们已经搞明白了插入函数迭代器失效的原因和解决的方法这里我们不多加深入这里我们就通过删除函数erase来再次进入到迭代器失效的问题上如下图 此时按照上述代码的写法我们就可以发现在删除数据的时候会导致_finish(end)的位置减减到pos(it)位置的前面这样就会导致地址不匹配pos再也找不到迭代器区间的地址这个问题也就是另一个经典的迭代器失效问题 所以当我们按照上述代码的写法只要有两个偶数同时在一起或者结尾是一个偶数的话就会导致跳过一个偶数或者导致_finish(end)的位置减减到pos(it)位置的前面这些情况都是迭代器失效的问题 本质就是pos(it)和_finish(end)不能匹配成功会发生跳跃和穿越的问题
解决方法
上篇博客我们知道解决这种问题就是使用返回值就行了目的就是为了让地址连续所以此时解决这个删除函数中迭代器失效问题最好的方式还是使用返回值如下代码 以上就是使用返回值的方式解决迭代器问题所以以后碰到删除和插入我们都应该要考虑一下是否涉及到迭代器失效的问题前提我们使用的遍历方式是迭代器或者范围for如果碰到这些问题导致程序崩溃此时我们也不要慌张第一时间就应该要想到使用返回值的范式就可以很好的解决这个问题迭代器实现问题SoSo啦
总只要我们使用了迭代器来遍历数据那么就要考虑到迭代器失效的问题哦
从模板再谈匿名对象
在之前学习类和对象的时候我们了解了匿名对象的概念知道它就是从 vectorint v; v.vector();直接写成vectorint().vector()的一个简单省略写法并且此时如果写成模板参数的形式就可以写成 T().push_back()表示的就是使用Tint类型去创建一个匿名对象然后使用这个对象去调用push_back(尾插函数)也就可以写成T(),表示的就是使用默认构造函数生成一个该默认构造函数类型的匿名对象并且最后为了更好的配合模板初始化问题的使用C的大佬是允许这样写的const T x T()使用给匿名对象起别名的方式来延长匿名对象的生命周期否匿名对象的生命周期只在该行代码出了作用域就失效所以以后看到这种写法我们不要感到奇怪当然更重要的是在以后我们遇到有关模板类初始化的时候我们也可以使用这样的写法。注意一定要加上const不可以写成T x T()因为调用默认构造函数的时候都是通过临时变量的形式临时变量是具有常属性的所以如果没有const语法上就是有问题的。
如下就是使用匿名对象去构造我的模板类自我实现构造函数
迭代器特性
从源码中的构造函数再看迭代器如下图 我们可以发现vector类的构造函数中有一个构造函数是支持传两个随机迭代器参数调用构造函数的本质就是允许传两个指针地址给给构造函数中的随机迭代器函数所以有了这个迭代器构造函数那么此时调用构造函数的时候就可以进行如下的调用 可以发现只要我们有了一个迭代器类型参数的构造函数那么此时不仅可以对vector类型的数据调用构造函数、对string类型的数据调用构造函数而且还可以对数组调用构造函数所以总的来说迭代器本上就是调用地址只要我们传递的参数是地址类型指针并且迭代器类型符合双向迭代器、单向迭代器、随机迭代器那么就可以使用迭代器来访问我们的数据。
总此时无论是迭代器失效问题还是迭代器使用问题我们都进行了进一步的理解所以迭代器的学习我们先告一段落不过在以后的链表和二叉树学习过程中我们肯定会再和它打交道了并且更加深入的了解它。
再谈深浅拷贝
搞定了迭代器这个重要的话题此时我们就再来看一个重要的问题深浅拷贝问题再谈原因当我们遇到了深拷贝之后新空间的数据还是一个类也就是还指向了一块空间还需要进行深拷贝此时就会因为如果只进行一次深拷贝的话那么导致旧空间中的数据指向的那块空间会被两个不同空间新空间、旧空间中的数据类型指针一起存储最后就会导致该空间被析构两次导致程序崩溃。 有了这个问题那么此时我们就需要再进一步的了解一下深浅拷贝问题了
第一个涉及这种场景调用拷贝构造的时候 第二个涉及这种场景扩容的时候
在谈这两个场景之前之前我们浅浅复习一下之前的知识this指针和赋值运算符重载
this指针
学习类和对象this指针是非常的重要的因为this指针是C大佬专门发明出来在类和对象中使用的目的就是为了可以让我们在调用类中的公有成员函数的时候可以少传一个参数默认this指针代表的就是该类对象并且this指针可以省略所以就导致我们可以不需要传参类对象就可以使用某个类对象如下图 我们可以发现我们的函数中只有三个参数但是此时我们却可以使用该类的私有成员变量原因就是我们的类对象d1去调用了该公有成员函数Init所以此时编译器就默认该对象d1就是this指针并且默认它的地址被传给了该公有成员函数Init并且默认这个地址参数是被this指针接收所以就导致在类中的公有成员函数都有一个默认的参数存在就是this指针有了这个默认的指针此时就允许该类的任意对象直接来调用该类中的公有成员函数。 注意此时小心空指针对象成为this指针的情况容易导致空指针解引用问题
赋值运算符重载
首先第一点赋值运算符重载是一个默认成员函数它是六大天选之子之一你不写编译器会自己调用无论是内置类型还是自定义类型但是只是浅拷贝如果涉及到深拷贝问题就需要自己去实现一个深拷贝的拷贝构造函数因为拷贝构造函数由于使用了赋值运算符所以也可以直接对内置类型和自定义类型进行初始化所以如果涉及到了深拷贝问题那么你就需要自己去实现赋值运算符重载和一系列的构造函数、拷贝构造函数因为编译器会优先调用我们自己实现的函数其次才是对自定义类型调用相应的默认成员函数。 六大默认成员函数和自定义类型、内置类型的关系总的来说就是两句话 默认构造函数和析构函数对内置类型不处理自定义类型调用其对应的函数 拷贝构造函数和赋值运算符重载不仅对自定义类型进行处理对内置类型也会处理但只是浅拷贝 从拷贝构造看深浅拷贝
复习完上述的知识我们就可以开始新知识的学习了问题如上描述深拷贝出来的新空间中还有深拷贝如下图
根据上图我们就可以充分意识到遇到类中类问题的解决方法和基本情形了所以以后想要使用memcpy函数就一定要考虑是否涉及深层次的深拷贝问题如果有就不可以使用memcpy函数而是去循环调用赋值运算符重载前提是该类具有赋值运算符重载。
从扩容函数看深浅拷贝
搞定了上述拷贝构造函数中的深层次深拷贝问题发现原因是因为memcpy只能进行浅拷贝的问题此时就想到我们的扩容函数中也使用了memcpy函数所以此时我们就应该要想到扩容函数是否也会涉及深层次的拷贝问题相信我答案是会的如图 因为道理都差不多这里不多做讲解
总我们对奇怪知识的理解又多了一点点深层次的深拷贝问题So、So
vector较完整代码如下
#includeiostream
#includestring
#includestdlib.h
#includestring.h
#includeassert.h
#includealgorithm//包含所有算法的头文件,例如sort
#includefunctional//排降序的时候用到
using namespace std;namespace wwx2
{templateclass Tclass my_vector{public://可以看出上述是把T给直接定义成了一个value_type然后使用value_type定义了const指针和普通指针所以本质上都只是T类型而已typedef T* iterator;//有普通版本的迭代器此时无论是因为模仿STL还是真的会用到我们都应该要给一个const类型的迭代器typedef const T* const_iterator;typedef T value_type;typedef size_t size_type;my_vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)//初始化列表初始化{}my_vector(size_type n, const T val T()) //和下述的那个resize同理使用T匿名对象调用默认构造函数然后增长匿名对象的生命周期: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){//所以无论这个类是什么类型在初始化的时候我们就是给给它的默认构造就行了reserve(n);for (size_type i 0; i n; i){push_back(val);}}//此处可以添加一个上述构造函数的int重载版本省略//使用迭代器区间的一个构造函数实现了这个函数此时就会导致下面的那个调用构造函数初始化时不知道调用那个了因为此时有两个构造函数就会导致类型不匹配问题因为使用迭代器的构造函数是要求传指针参数的不可以是整形参数所以需要改进一下萝卜青菜区分开来template class InputIterator//此时就是一个模板类中有模板函数的写法本质上是因为迭代器的多样性不可以把迭代器的类型给写死要是可变的所以就使用模板的方式my_vector(InputIterator first, InputIterator last)//就是一个允许类模板的成员函数是一个函数模板: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr){while (first ! last)//迭代器区间[first,last){push_back(*first);first;}}//上面的两个构造函数对比如果下面传上来的参数是两个整形那么此时因为第一个构造函数的第一个参数是一个无符号数所以此时需要进行类型的转换//而第二个构造函数的两个参数由于都是指针所以就可以直接接收不需要什么类型转换所以第二个构造函数就是下述传参更爱吃的萝卜//所以如果是两个整形使用了第二个构造函数那么此时就会导致对整形就行解引用的情况发生所以就有问题了//这个位置还可以加一个给拷贝构造使用的构造函数如果没有这个拷贝构造函数而是去调用构造函数的话这边就会导致经典的浅拷贝问题//my_vector(const my_vectorT v)//因为调用拷贝构造的时候传参传上来的是一个vector的对象所以这边的参数也要写成vector不可以像上述一样写成别的类型//{//因为上述已经有了一个有缺省参数的构造函数所以这里不需要再给总的来说还是拷贝构造的特性// reserve(v.capacity());//深拷贝不然就会导致同一块空间析构两次// for (auto e : v)// {// push_back(e);//这个是因为复用了reserve和push_back不传统// }//}//my_vector(const my_vectorT v)//传统一些的深拷贝写法//{// reserve(v.capacity());// memcpy(_start, v._start, sizeof(T) * v.size());//这步需要仔细研究一下本质还是那个道理反正三指针问题一定要给它搞定// _finish _start v.size();//这边因为_finish是由数据个数决定的所以新开辟的空间之中有多少个数据是会改变的所以这边的_finish是需要自己更新一下的不可以依靠reserve中的_finish但是reserve中的容量是可以使用的因为开辟空间的大小是不会改变的//}my_vector(const my_vectorT v)//最传统的深拷贝写法{_start new T[v.capacity()];//memcpy(_start, v._start, sizeof(T) * size());//所以为了解决深深拷贝的问题此时就不可以使用memcpy函数而是去循环调用赋值因为赋值运算符本质上就是深拷贝实现的for (size_type i 0; i v.size(); i)//调用赋值运算符然后通过复制运算符再去调用拷贝构造{ _start[i] v._start[i];//此时这样写我们就解决了深拷贝中还要深拷贝的问题本质就是构造函数中嵌套构造函数}//并且因为按照上述那样写对内置类型并没有影响因为复制对内置类型来说也是可以用的赋值重载本来就是六大默认函数之一编译器会自己调用_finish _start v.size();_end_of_storage _start v.capacity();//reserve是会处理容量的所以没有使用reserve的时候就要自己把容量的大小给处理一下}~my_vector(){delete[] _start;_start _finish _end_of_storage nullptr;}iterator begin(){return _start;//通过临时变量返回就是类型传值返回}iterator end(){return _finish;//同理}const_iterator begin()const{return _start;}const_iterator end()const{return _finish;}size_type capacity()const//像这种函数一定要记得加上我们的const{return _end_of_storage - _start;}size_type size()const{return _finish - _start;}void resize(size_type n, T val T())//实现了reserve此时resize复用就行只是初始化的细节注意一下和size的大小注意一下就行 (所以此时我们就需要多给一个缺省参数来进行默认的初始化新开辟的空间)就是防止你没有给第二个参数只给了第一个参数那此时我也可以把空间初始化{//上面那个缺省值的初始化是非常的神奇的目的因为此时的vector是一个泛型编程是没有具体类型的所以不可以用0来初始化所以就要用一个模板参数然后就是通过一个匿名对象的方式去调用默认构造函数if (n size())//这个不是缩容这个属于删除数据因为size是和数据紧紧挂钩的{_finish _start n;//这个条件在尾插的时候扩容是不可能存在缩容删除数据的唯一可能的就是我们直接调用这个函数然后进行传参的时候只有这种情况才有可能导致删除}else{if (n capacity()) //这个条件就是传说中的只有你比我小我才扩容 {reserve(n);//开空间}while (_finish ! _start n)//加 初始化{*_finish val;//此时已经有空间了所以就不需要使用定位new的方法直接赋值就行_finish;}}}value_type operator[](size_type pos)//并且此时返回的是一个引用就是返回别名目的节省构造防止临时变量的常属性{assert(pos size());return _start[pos];//就是返回这个pos位置在_statr中的那个下标位置因为此时是指针所以准确的来说应该是一个地址位置}const value_type operator[](size_type pos)const//就是多重载一个const类型的给给const对象使用萝卜青菜给有所爱{assert(pos size());return _start[pos];}void reserve(size_type n){//这边肯定是要涉及深浅拷贝问题的(并且为了防止缩容问题这边还要进行判断)if (n capacity()){//使用下面调换两个指针的顺序是可以解决的但是不是很好所以我们这边直接先把size()的值给接收起来就行size_type sz size();//这样直接使用sz就行了不需要再使用size(),也就是不需要再考虑finish和start的位置随便它去变跟我都没关系iterator tmp new T[n];//此时这里不需要考虑加1的问题只有像string这种需要存一个\0的这种我们才需要多开一个空间给给它使用if (_start ! nullptr)//此时的new是不需要想malloc一样就行开辟成功和失败的判断的这个只是为了单纯判断一下_start中是否有数据{// memcpy(tmp, _start, sizeof(T) * size());//由于自定义类型不适用所以改进一下for (size_type i 0; i sz; i)//深深拷贝的专业写法间接多调用一次拷贝构造{tmp[i] _start[i];}delete[] _start;//这个就是经典的防止空间太大太小问题直接开空间然后拷贝然后直接把原空间给删除}//_start tmp;//注意因为此时是重新开空间释放旧空间所以此时的tmp就是我们的start//注意此时下述的size()是不会因为finish和start的地址改变而改变的 (不会因为重复调用而改变)一直都是同一个值也就导致可以直接使用tmpsize()//_finish tmp size(); //如果此时是先把tmp给给_start然后再加加size(),此时就会导致finish还是0而start已经是tmp然后又因为size就是finish-start就会导致size是一个负值也就是-start然后再用startsize那么刚好就是0最后赋值给finish此时finish就还是0所以就会导致后面push_back的时候对空指针解引用的问题所以此时为了解决这个问题此时就不敢直接先给给start_start tmp;//先给给finish再给给start就行很好的解决finish为空的问题_finish tmp sz;//提前记录size的好处_end_of_storage tmp n;//此时的这个是通过tmp指针的首地址然后加上16个字节就是首地址向后走走16个字节得到的就是此时的容量}}void push_back(const T x){//因为使用的是模板类所以这里的参数类型都是直接给一个T参数类型就可以了//从刚刚的STL源码中我们可以发现的是它的push_back使用的是内存池开空间的形式就是定义new加定义构造//我们这里使用不了我们就直接使用正常的形式就行如果想要使用的话就要在定义模板参数的位置给一个内存池的参数if (_finish _end_of_storage){reserve(capacity() 0 ? 4 : capacity() * 2);//此时的reserve函数中使用的是赋值运算符重载而不是memcpy可以放心大胆的使用}*_finish x;//这步就是尾插的经典步骤上述的判断只是为了防止空间不足而已但是由于我们的_end是一个原生指针所以这里想要直接在尾部赋值就需要对这个尾部指针进行解引用_finish;}void pop_back(){assert(!empty());//切记assert给的是真--_finish;//这种如果直接用不检查的话就会导致删多了的话有越界问题并且因为此时是迭代器的写法地址问题就会导致循环找地址然后就导致无限打印地址直到地址匹配到}iterator insert(iterator pos, const T val){assert(pos _start);assert(pos _finish);//还是那个道理assert中的条件是真条件//并且由于这边我们使用了_finish这个位置就会导致如果容量刚好等于_finish的时候越界所以这个位置也要进行一个容量的检查if (_finish _end_of_storage){size_type len pos - _start;//先记录len的作用就是解决迭代器失效问题目的更新pos指针reserve(capacity() 0 ? 4 : capacity() * 2);pos _start len;//扩容后再更新解决迭代器失效问题}iterator end _finish - 1;while (end pos)//此时因为pos的类型是地址所以不存在-1变成无符号size_t所以不存在死循环没有问题{*(end 1) *end;//此时的意思就是把最后一个数据赋值给0然后把刚刚那个位置腾出来然后循环--end;}*pos val;_finish;return pos;}iterator erase(iterator pos){assert(pos _start);assert(pos _finish);iterator start pos 1;while (start ! _finish){*(start - 1) *start;start;}--_finish;return pos;//这个位置不敢给成start因为本质就是为了更新pos}bool empty(){return _start _finish;//当_finish--到_start的时候就是空就是不允许的}private:iterator _start;//因为STL源码中是直接使用三个原生指针所以这边我们模仿它我们也直接使用三个原生指针就行iterator _finish;iterator _end_of_storage;/* iterator _start nullptr;iterator _finish nullptr;iterator _end_of_storage nullptr;*///这样写就可以不需要在构造函数的初始化列表位置写初始化了因为到时候他自己会调用这下面的缺省参数};void test_my_vector1(){wwx2::my_vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);for (size_t i 0; i v.size(); i){cout v[i] ;}cout endl;my_vectorint::iterator it v.begin();//所以本质我发现使用三指针的写法好像就是为了可以更好的使用迭代器因为迭代器的本质就是地址的加加减减前提地址要连续while (it ! v.end())//不连续就会导致迭代器失效问题{cout *it ;it;}cout endl;for (auto e : v)//有了迭代器就有迭代器的小儿子范围for{cout e ;}cout endl;}void test_my_vector2(){wwx2::my_vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.pop_back();v.pop_back();my_vectorint::iterator it v.begin();my_vectorint::const_iterator cit v.begin();//反正就是萝卜青菜给有所爱数据类型是什么就调用什么不怕没有想要什么就有什么while (it ! v.end()){cout *it ;it;}cout endl;}templateclass Tvoid Function(){ T x T();//这个就直接用一个int类型来辅助理解就行int x int(); 可以看出此时的int();就是我们之前学的匿名对象其中int 空表示的就是匿名对象而int()后面的括号其实表示的是此时拿着这个int空匿名对象去调用某一个函数()括号中存放的本就应该是你要调用的这个函数需要的参数cout x endl;//总而言之目的就是为了让任意类型都可以调用默认构造然后任意类型都可以初始化}void test_my_vector3(){//int i int();//这个的意思就是使用int()匿名对象然后去调用构造函数然后延长匿名对象的声明周期把它给给新的对象//int j int(1);//支持这种写法本质上还是为了支持模板可以使用也就是上述resize的缺省参数的写法很好的解决模板初始化问题//int* p int*();//指针类型不支持直接把匿名对象给给指针类型Functionint();Functionint*();Functiondouble();}void test_my_vector4(){wwx2::my_vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);cout v.size() endl;cout v.capacity() endl;v.resize(10);for (auto e : v){cout e ;}cout endl;}void test_my_vector5(){wwx2::my_vectorint v;v.push_back(1);//切记push_back也是会调用扩容函数的v.push_back(2);v.push_back(3);v.push_back(4);//此时就很神奇的会涉及到insert是否扩容问题如果数据是在push_back的过程insert就不涉及扩容如果insert涉及扩容那么此时就有迭代器失效的问题本质就是地址不连续//v.insert(3, 30);//聪明的我居然写成了这样真的是函数都调不明白啊但是也刚刚好可以引出我们的find函数//当然此时也不一定一定要使用find因为此时可以直接使用迭代器充分表明我的不聪明v.insert(v.begin(), 0);v.insert(v.end(), 6);v.insert(v.begin() 3, 30);//充分表明迭代器是真的好用促进我们通过变量看地址的好习惯auto pos find(v.begin(), v.end(), 3);//此时就是把我的迭代器区间传给了find函数这样就可以使用find函数在迭代器区间中寻找3这个元素if (pos ! v.end())//这里这样写的目的主要是为了防止越界{pos v.insert(pos, 30);//像这种一直插一直插这种就会导致一个问题经典的迭代器失效问题本质就是地址不连续了}//并且此时如果上述这样写那么pos在内存中的位置是不会改变的但是由于insert因为内存不足开空间导致start和finish的位置发生了改变间接导致begin和end迭代器发生了改变导致迭代器失效(*pos);//意思就是拿更新后的pos地址中的元素加加一下但是本质上是想要在3的地方加加最后会变成在30的地方加加原因就是pos指向的那个地址是不变但是地址中的数据在挪动并且此时因为insert中的pos是个形参所以不会影响我们外部的pos所以此时最好使用返回值直接把pos返回给我们//并且此时由于使用引用返回需要有临时变量常属性所以不推荐使用所以这边我们是通过返回值的方式解决这个问题的//总pos失效后我们都不推荐使用(*pos);所以不敢这样写for (auto e : v)//迭代器一失效也就是begin和end的地址发生了改变那么此时就会导致pos地址虽然不改变但是它指向的空间发生了改变从原来的begin和end的地址变成了一块未知的地址因为begin和end已经因为扩容拥有了新地址并且把原来的地址给释放了所以pos指向的地址就是一个未知地址此时pos指针就是一个野指针{//总开空间之后导致pos指向的迭代器地址被释放导致野指针问题这也就是第一种经典的迭代器失效问题cout e ;//所以明白了原理之后此时的解决方法就是更新一下pos指针指向的空间依据pos和start的相对位置不改变来更新} cout endl;}void test_my_vector6(){wwx2::my_vectorint v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4); v.push_back(5);auto pos find(v.begin(), v.end(), 2);if (pos ! v.end()){v.erase(pos);}(*pos);//此时这个不是解引用的意思可以算是对*这个运算符重载的意思本质上*的运算符重载就是在获取一下我想要的功能或者是数据for (auto e : v)//并且此时这种删除之后再访问的方式是很不好的因为如果按照erase代码来说删除最后一个及时把finish的位置往前挪动一个此时如果再去访问刚刚4的pos位置就会导致对一个空地址解引用此时就出问题了{ //所以总的来说删除数据后是不允许进行数据访问的并且此时数据删除也是伴随着迭代器失效的问题的因为位置关系发生了改变也就是导致地址不连续迭代器失效的本质cout e ;}cout endl;}void test_my_vector7(){wwx2::my_vectorint v;v.push_back(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(50);for (auto e : v){cout e ;}cout endl;//此时要求删除所有的偶数//std::vectorint::iterator it v.begin();wwx2::my_vectorint::iterator it v.begin();while (it ! v.end()){if (*it % 2 0){it v.erase(it);//此时因为我们已经通过返回值可以直接拿到it的位置了所以就不需要每次都因为数据覆盖之后我的it就已经是被覆盖成了新的数据此时只要再判断一下这个数据是不是偶数就行}else//因为可以连续拿到it位置的数据所以it只有在不是奇数的时候需要使用{it;}}for (auto e : v){cout e ;}cout endl;}void test_my_vector8(){//wwx2::my_vectorint v(10, 5);//wwx2::my_vectorint v(10u, 5);//解决方案一在整形后面加个u表示无符号的意思本质就是让它去找那个无符号的参数匹配wwx2::my_vectorint v(10u, 5);//解决方案二就是把上面那个函数提供一个int类型的重载函数这个似乎有点傻傻的样子//迭代器构造函数的正确使用方法wwx2::my_vectorint v2(v.begin(), v.end());//wwx2::my_vectorint v(v.begin(), --v.end());//此时这个一开始报错的原因也就是不让我加加减减的原因就是涉及临时变量常属性问题//因为我们一开始的时候写的begin和end函数使用的是传值返回解决方法就是使用传引用返回但是没什么必要避免风险导致first和last被间接改变wwx2::my_vectorint v3(v.begin() 1, v.end() - 1);//避免临时变量常属性问题 先返回返回之后再加加减减//因为我们的构造函数是使用随机迭代器实现的所以是可以接收任何类型的迭代器的例如std::string s1(hello world);wwx2::my_vectorint v4(s1.begin(), s1.end());//所以传什么类型的迭代器都是没什么问题的迭代器就是牛只要类型匹配就行可以强转//或者可以这样玩本质就是在玩地址int arr[] { 1,2,3,4,5,6,7,8,9,10 };//随机迭代器爱咋玩咋玩wwx2::my_vectorint v5(arr, arr 10);for (auto e : v){cout e ;}cout endl;for (auto e : v2){cout e ;}cout endl;for (auto e : v3){cout e ;}cout endl;for (auto e : v4){cout e ;}cout endl;for (auto e : v5){cout e ;}cout endl;}void test_my_vector9(){int arr[] { 0,10,4,3,2,6,9,8,1,7,5 };my_vectorint v(arr, arr 10);for (auto e : v){cout e ;}cout endl;cout 排序之后的情况: endl;sort(v.begin(), v.end());//总的来说就是算法库里面存在着一个迭代器形式的sort函数反正迭代器就是神本质指针地址yydsfor (auto e : v){cout e ;}cout endl;sort(arr, arr sizeof(arr) / sizeof(arr[0]));//总归有了迭代器函数变得更加的好用了更加方便了可爱并且此时是 默认是升序降序需要控制一下for (auto e : v){cout e ;}//排降序的写法cout 降序 endl;//greaterint g;//这个东西叫仿函数具体以后学习//sort(arr, arr sizeof(arr) / sizeof(arr[0]),g);//想要排降序就需要多传一个参数具体原理不了解先看看怎么使用就行//这种写法很适合直接写成匿名对象的形式sort(arr, arr sizeof(arr) / sizeof(arr[0]), greaterint());for (auto e : v){cout e ;}}//此时这边有一个很重要的知识点我们使用sort排序的是我们自己的容器vector原理是因为我们包含了头文件去调用了别的头文件中的函数这里好像埋了一个炸弹没有解决void test_my_vector10(){my_vectorint v(10u, 5);//调用自己实现的构造函数for (auto e : v){cout e ;}cout endl;my_vectorint v1(v);//调用拷贝构造(本质还是在调用构造函数只是此时是通过this指针用v这个已经初始化的对象去初始化v1这个对象而已)注意和赋值的区别区别就是赋值是两个都已经实例化的对象而拷贝构造是一个已经实例化一个还没有for (auto e : v1){cout e ;}cout endl;my_vectorstd::string v2(3, 1111111111111111);for (auto e : v2){cout e ;}cout endl;my_vectorstd::string v3(v2);//这句代码的意思就是使用一个string类的vector模板类来拷贝构造另一个新对象v2//此时因为使用v2去初始化v3的时候回去调用reserve函数防止空间太大太小问题都是直接开空间然后memcpy最后delete原空间//这样写的好处有两点一个是解决深浅拷贝问题前提是内置类型一个就是解决空间浪费和不足问题//但是此时如上述一样我们不再是内置类型而是自定义类型并且此时这个自定义类型中也涉及到了深浅拷贝问题就是也有一块空间//那么此时就是变成是深深拷贝问题需要进行两次的开辟空间//所以如上述我们这样写只调用了一次reserve(new)函数那么就会导致 原本应该有两次的深拷贝变成了一次就又会导致同一块空间被析构两次的问题了for (auto e : v3){cout e ;}cout endl;//所以此时我们按照这个写法我们就可以发现我们的程序因为同一块空间释放了两次而导致崩溃了//本质的原因就是我们使用了memcpy它只会进行浅拷贝把原空间中的地址拷贝到新空间中不会进行深拷贝//所以解决的方法就是不能使用memcpy而是自己再去实现一个深拷贝出来因为memcpy是库里面规定的写法不会进行深拷贝//所以memcpy只有在进行内置类型深拷贝的时候可以使用而在拷贝自定义类型的时候最好就不使用memcpy函数//解决原理是上述所说的那样但是因为我们已经实现了一个深拷贝了模板类中的自定义类型的数据的深拷贝问题我们是摸不到的所以此时我们并不能自己去实现深拷贝例:string类中的私有对象我们是访问不了的//此时根据这个拷贝构造的问题此时我们就还可以发现我们在扩容的时候复制数据的时候使用的也是memcpy函数//所以扩容使用memcpy对于普通的内置类型处理是没有什么问题的但是如果是自定义类型那么此时就连扩容本质上也是有问题的因为扩容也是需要把旧空间中的数据拷贝到新空间的// 并且由于扩容的时候会把原来的空间释放掉那么就会导致你的空间中所有的自定义类型上的指针类型都是野指针//所以同理无论是扩容还是构造函数只要需要对自定义类型进行拷贝那么此时就不仅要涉及到外部空间的深拷贝还要注意到空间内的数据的深拷贝问题//所以解决方法和拷贝构造函数是一样的就是使用赋值运算符重载然后通过它间接的再去调用一次拷贝构造例:string类中的私有对象我们是访问不了的//但是此时由于不是所有的类都会去重载赋值string有重载赋值但是vector类却没有重载赋值所以此时导致vector类中类此时就算是使用了赋值运算符重载//也不可以进行深深拷贝问题还是解决不了问题还是一个深层次的浅拷贝问题所以还是会崩溃string可以但vector类不可以//本质上还是没有两次的深拷贝构造函数的实现没有重载赋值运算符//并且此时我们是要注意到我们的构造函数是可以使用现代化的写法的}
}
int main()
{wwx::vectorint::iterator it;//cout typeid(it).name() endl;//typeid函数就是为了获得这个变量的类型是什么而已wwx2::test_my_vector1();//测试尾插wwx2::test_my_vector2();//测试尾删wwx2::test_my_vector3();//测试匿名对象wwx2::test_my_vector4();//测试resizewwx2::test_my_vector5();//测试insert和erase本质扩容导致迭代器失效问题wwx2::test_my_vector6();//测试插入函数中迭代器失效wwx2::test_my_vector7();//测试编译器对erase的控制std是直接报错只要删除了数据就是报错wwx2::test_my_vector8();//测试构造函数wwx2::test_my_vector9();//测试随机迭代器wwx2::test_my_vector10();//测试深浅拷贝问题return 0;
}
代码注释都比较的详细 总结星期五就该发的文章留到了今天充分表明摆烂我也是专业的。