假如现在不扩容
上图就是下图对应的另外一种迭代器失效
在实际应用时,我们并不知道insert后是扩容还是没有扩容,从而判断出旧p是否是野指针,还能不能用,所以我们认为使用insert之后不管是否扩容,我们都认为迭代器失效,不能用旧p位置,要使用旧p也要将它更新再用,不同平台对于使用insert之后若再次使用旧p有不同反应,vs会直接报错,g++不会
g++下使用insert扩容后,依旧给旧p位置赋值(如下图),程序居然也没有崩溃,即系统没有检查出来。万一系统将已经释放的空间拿去重新使用,会直接将其中数据修改,很危险。
g++使用insert后再使用旧p,扩不扩容都不报错。vs使用insert后再使用旧p,扩不扩容都报错
所以,使用insert之后,vs做的处理比g++好
11.erase模拟实现
删除后是否认为删除位置及以后位置的迭代器失效?
删除后不同平台是否会进行缩容处理,若缩容还要注意删除位空间是否被缩容归还给系统
完成模拟
1.现在删除插入值中的偶数
完成代码,运行正常
2.但有多个偶数时,删不干净
分析:发现第一个偶数2,利用erase(删除it位置2,it后所有元素都自动向前移位,finish--),此时it依旧是原来指向2的旧指针,it++,就指向了4。发现偶数4(第一个),利用erase(删除it位置4,it后所有元素都自动向前移位,finish--),此时it依旧是原来指向偶数4(第一个)的旧指针,但此时该位置是第二个4,it++,就跳过4,指向了5
3.有时奔溃 如下插入1234
在将2删除,it后所有元素向前移动一位,finish--,it依旧指向原先2位置,该位置现在是3,跳过3。it指向4,erase删除4(函数内it==end(),finish--,此时finish指向4),it++,此时it指向4后的位置,此时it已经越界,且it在后,finish在前,两者已经错开,函数内it、end永远不相等。若再次调用erase,此时finish=pos,断言报错。
以上三种情况,在g++运行,结果也会发生和在VS上运行一样
但是用vs库里的vector,都进行了强制检查,一个都过不去,和之前insert一样,因为erase之后还在用it++,。
库中的erase删除完成后(移位完成)会返回一个迭代器,迭代器指向被删除元素的下一个元素位置的指针。所以对于使用库中的erase后,不管平台锁不锁荣,我们也都认为旧的迭代器失效,要用也要用erase返回的新指针更新下再用。我们模拟实现时并没有返回值
正确使用模拟实现erase的代码应该是这样,如果是偶数时才会删除,之后it不++,如果是奇数才it++
几乎所有容器erase后迭代器失效,但insert不一定,看是否扩容
| 操作 | 失效范围 | 有效部分 | 返回迭代器 |
|---|---|---|---|
erase(pos) | pos以及pos 之后全部迭代器 | pos 之前迭代器有效 | 指向被删除元素的下一个元素,有效 |
insert(pos,val) 不扩容 | pos以及pos 之后全部迭代器 | pos 之前迭代器有效 | 指向新插入的元素,有效 |
insert(pos,val) 触发扩容 | 容器全部迭代器全部失效 | 无 | 指向新插入的元素,有效 |
string也有迭代器失效
12.resize
下图两种写法一样 自定义模板内给缺省值一般都是如下图T()一样给
模拟实现
13.拷贝,析构函数
如下图 v v1 浅拷贝了,若此时析构不会崩溃,因为自动生成的析构函数不会释放申请的资源,从而造成内存泄漏
补上析构函数,程序崩溃, v v1 浅拷贝了
所以需要手动写深拷贝的拷贝构造
&:引用。e是元素的引用,不是拷贝副本,e是对v中每一个元素的别名,不会拷贝元素
这里显示地写了构造,自动生成的默认构造就不会生成了
所以需要显示写勾造函数,因为我们给了缺省值且无自定义成员变量,所以仅仅如下图写即可
运行成功
改名称为
回看拷贝构造,发现里面的push_back可能还要扩容,这样效率有点低
改进,提前开和被拷贝对象有效长度一样大的空间,push_back只在该空间内放值即可
14.赋值 operator=
传统写法
clear()函数不用将申请的空间释放,只是将数据清空即可
运行成功
现代写法
实参是v3,形参v是传值,会调用拷贝构造函数,把v3深拷贝一份,生成局部临时对象v,:交换this(v1)和局部对象v的三个指针。函数结束,局部变量v出作用域,自动调用析构函数,把 v1 原来旧堆内存释放掉。return *this,支持链式赋值 a=b=c。这里的swap是我们自己模拟出来的
14.用迭代器区间构造 的模拟实现
模拟实现
测试
运行成功
我们先在验证其他容器调用它的情况 添加list
15.模拟用n个val初始化vector对象的函数,
将string类包进来
再次测试
1.什么叫 “迭代器”?
迭代器像一个指向容器中某个元素的游标,需满足以下一个或多个功能:
解引用 *it:拿到它指向的元素
++it:向后移动,指向下一个元素
!= / ==:判断是否走到末尾
迭代器根据拥有的功能,分为不同迭代器。下一节进一步介绍
InputIterator只是模板参数名字,编译器不会把它当成迭代器(也不会校验你传入的是否是迭代器),只是使用时期望使用者传入的实参是迭代器。
模板形数匹配规则:同一个模板参数InputIterator,要求两个实参的类型可以推导成同一个类型。若满足此规则,就可以优先调用该函数模板
上图显示报错报到迭代器区间初始化上了,为什么?
两个实参的类型可以推导成int型(在与两种识别方式比较后选择最匹配的函数),而后*first相当于是对int进行解引用,当然报错
解决方法,直接给更加合适的函数
16.
测试 vector容器内是string类对象(没扩容·) 成功运行
再次插入(扩容)
分析
只要vector内是申请了资源的自定义类型,memcpy就会浅拷贝,出错
解决办法
将memcpy那句代码替换为循环将旧空间元素(string类对象)赋值给新空间元素(string类对象),这样的话相当于调用string类对象的运算符重载函数(深拷贝)了,
重新运行
长的函数定义记得放到类外,若是函数模板到时候记得加模板参数,::前不要写类名记得写类型,如下