0


【C++】vector的模拟实现@STL —— 迭代器失效问题

vector的模拟实现@STL

本文将按照前后依赖逻辑链完成vector的模拟实现,重点介绍迭代器失效问题。完整的

.h

文件附在最后(含测试代码)

正文开始@边通书

在源码中,vector中声明了如下成员变量 ——

template<classT,classAlloc= alloc>classvector{public:typedef T* iterator;private:
    iterator start;
    iterator finish;
    iterator end_of_storage;};

它们的作用等同于我们之前的

_a

_size

_capacity

——

1. (constructor) & (destructor)

❤️ 构造函数

  • 无参构造
  • 迭代器区间构造 (后面实现了push_back再写)

类似顺序表,初始化一定要都给空,不然push_back就会出问题。

//1.无参构造vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}

❤️ 析构函数

~vector(){if(_start){delete[] _start;
            _start = _finish = _endofstorage =nullptr;}}

2. 一系列基本接口

2.1 size & capacity

    size_t size()const{return _finish - _start;}

    size_t capacity()const{return _endofstorage - _start;}

2.2 []

  • _start原生指针,直接[下标]访问
    T&operator[](size_t n){assert(n <=size());return _start[n];}const T&operator[](size_t n)const{assert(n <=size());return _start[n];}

2.3 iterator

普通迭代器 & const迭代器,const对象调用const成员函数返回const迭代器,老生常谈了

    iterator begin(){return _start;}

    iterator end(){return _finish;}

    const_iterator begin()const{return _start;}

    const_iterator end()const{return _finish;}

支持迭代器就支持范围for.

3. 尾插尾删

3.0 reserve & resize

插入元素要考虑扩容问题。

❤️ reserve

同样需要深拷贝

voidreserve(size_t n){if(n >capacity()){
            T* tmp =new T[n];
            size_t sz =size();//若原来已有空间,则需要释放旧空间、拷贝数据if(_start){/*memcpy(tmp, _start, sizeof(T)*sz);
                delete[] _start;*/for(size_t i =0; i < sz; i++){
                    tmp[i]= _start[i];}delete[] _start;}
            _start = tmp;
            _finish = _start + sz;
            _endofstorage = _start + n;}}

注:

  • 注意更新*_finish*时,要提前把sz保存起来。// 错误示范,请勿模仿_start = tmp;_finish = _start +size();_endofstorage = _start + n;这样是有问题的,因为size()是指针-指针,预期的是旧空间的_finish - _start(0),但是start已经更新了,减完就是负值。

  • 拷贝数据不能简单地memcpy ,这样虽然vector是深拷贝的,但其中的对象仍然是浅拷贝的。对于int没有问题,但是对于自定义类型就出问题了。因此我们需要更改 ——- 若T是int,一个一个拷贝,没问题;- 若T是自定义类型,一个一个拷贝,调用的是T的深拷贝赋值(建立在赋值重载的基础上),也没问题。

❤️ resize

resize经典的三种情况,老生常谈了

voidresize(size_t n,const T& val =T()){if(n <=size()){
            _finish = _start + n;}else{if(n >capacity()){reserve(n);}while(_finish != _start + n){*_finish = val;
                _finish++;}}}

说明 ——

  • 匿名对象像临时对象一样,具有常属性不可修改,必须加const
  • 匿名对象T()(生命周期在当前行)被const引用,会延长它的生命周期。引用销毁后,随之销毁(可验证)。
  • resize默认填充的是T类型的缺省值,int类型就是0;指针类型就是空指针;若是自定义类型,比如vector,那么就是调用的vector的构造函数,构造vector的匿名对象
  • C++中为了迎合模板需要,内置类型int等可认为有构造函数

3.1 push_back & pop_back

❤️ push_back

voidpush_back(const T& x){if(_finish == _endofstorage){reserve(capacity()==0?4:capacity()*2);}*_finish = x;
        _finish++;}

❤️ pop_back

voidpop_back(){assert(_finish > _start);
        _finish--;}

4. 构造 & 拷贝构造 & 赋值重载

❤️ 迭代器区间构造

//2.迭代器区间构造template<classInputIterator>vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){while(first != last){push_back(*first);
            first++;}}

说明 ——

  • 一个类模板的成员函数又可以是一个函数模板,这样,迭代器区间[first,last)可以是自己类型的迭代器,又可以是任意类型的迭代器。只要这个迭代器解引用的数据能和T匹配。
  • 同样的,一定要初始化,否则一会儿要push_back时候就会出问题(当初我写顺序表时候就吃过这个亏)。
  • 关于InputIterator名称的含义:函数模板的模板参数传迭代器区间是有命名规范的迭代器分为四类,用来访问容器 ——[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sVR8DFyI-1647934598040)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220321212840312.png)]学习继承后可以有更好的理解,子类满足父类的所有特征,同时又具有新的特征。子类的迭代器都可以给父类的,反过来不行。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDq8XSPb-1647934598042)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220322100034824.png)]因此,虽然语法上模板参数可以是任意类型,但是又有一定要求。

❤️ 拷贝构造 - 现代写法

借助构造,实现过string类,这就是老生常谈了

voidswap(vector<T>& v){
        std::swap(_start,v._start);
        std::swap(_finish, v._finish);
        std::swap(_endofstorage, v._endofstorage);}// 拷贝构造2 - 现代写法 - 借助构造// v2(v1);vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){
        vector<T>tmp(v.begin(), v.end());swap(tmp);//this->swap(tmp);/*swap(_start, tmp._start);
        swap(_finish,tmp._finish);
        swap(_endofstorage, tmp._endofstorage);*/}
  • 必须初始化置空,否则换给tmp,出作用域调用析构函数销毁时,会出问题(不能free一块是随机值的空间)

❤️ 赋值重载 - 现代写法

  • 复用拷贝构造
  • 同时一石二鸟,送外卖帮扔垃圾哈哈。
// 赋值重载// v1 = v3;
    vector<T>&operator=(vector<T> v){swap(v);return*this;}

这里是传值调用,v是v3拷贝构造来的。在此,我们再次重新理解拷贝构造不能传值必须传引用,因为拷贝构造要先传参,传参又是一次拷贝构造,这样下去无限递归了!

5. 迭代器失效问题

5.1 insert

我们按照惯常的逻辑实现insert:检查空间、挪动数据、插入数据(代码如下)。测试代码,发现insert插入第5个数据(即扩容时),出现了随机值

// 错误示范,请勿模仿voidinsert(iterator pos,const T& x){assert(pos >= _start);assert(pos <= _finish);if(_finish == _endofstorage){reserve(capacity()==0?4:capacity()*2);}

        iterator end = _finish -1;while(end >= pos){*(end +1)=*end;
            end--;}*pos = x;
        _finish++;}

测试代码 ——

//测试insert - 迭代器失效问题voidtestVector4(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);

        vector<int>::iterator it =find(v.begin(), v.end(),2);if(it != v.end()){
            v.insert(it,20);}for(auto e : v){
            cout << e <<" ";}
        cout << endl;}

这是因为若insert时发生了扩容,会导致it指向的空间被释放,此时it指向着已被释放的空间是野指针,这种问题,我们就叫做迭代器失效

⭐️ 因此我们通过,增容时,提前算出相对位置,更新pos来解决。然而此时外边的it仍然是失效的,则通过传返回值(An iterator that points to the first of the newly inserted elements.),之后还需要使用it,再根据需要接收返回值(不能传引用,传引用会引发其他问题)

❤️ 终版insert

    iterator insert(iterator pos,const T& x){assert(pos >= _start);assert(pos <= _finish);if(_finish == _endofstorage){// 扩容会导致迭代器失效,需要提前计算更新pos
            size_t len = pos - _start;reserve(capacity()==0?4:capacity()*2);
            pos = _start + len;}

        iterator end = _finish -1;while(end >= pos){*(end +1)=*end;
            end--;}*pos = x;
        _finish++;return pos;}

对应测试代码 ——

//测试insert - 迭代器失效问题voidtestVector4(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);

        vector<int>::iterator it =find(v.begin(), v.end(),2);if(it != v.end()){
            it = v.insert(it,20);// 若insert时发生了扩容,会导致it指向的空间被释放// it本质上是野指针,这种问题,我们就叫做迭代器失效}
        v.insert(it,10);for(auto e : v){
            cout << e <<" ";}
        cout << endl;}

运行结果 ——

5.2 erase

我们按照惯常的逻辑实现erase:挪动数据覆盖。因为不涉及重新开空间深拷贝,那这块儿确实没出现野指针的问题。

// 错误示范,请勿模仿voiderase(iterator pos){assert(pos >= _start);assert(pos < _finish);assert(_start < _finish);

        iterator begin = pos +1;while(begin < _finish){*(begin -1)=*begin;
            begin++;}
        _finish--;}

现在,要求实现删除v中所有偶数,测试代码 ——

//测试erase - 迭代器失效问题voidtestVector5(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);//v.push_back(5);//删除数组中的偶数
        vector<int>::iterator it = v.begin();while(it != v.end()){if(*it %2==0){
                v.erase(it);}
            it++;}for(auto e : v){
            cout << e <<" ";}
        cout << endl;}

测试如下三种场景,却发现 ——

12345-> 正常(其实是巧合)1245-> 没删利索           
1234-> 崩了           
  • 1245为例 ,这代表的是相连偶数的情况。删完2(挪动数据)后it++,导致后一个偶数没有判断,4刚好就逃过去了;
  • 再分析1234,这代表的是最后一个数是偶数的情况。erase最后一个偶数,it++,此时it和_finish刚好错过,it如脱缰野马,越界访问(这时候你就说了,把!=改成<不行吗,这不符合迭代器的一般使用)

总结,erase(it)后,it指向的位置意义已经变了,直接++,会导致一些意料之外的结果,这也是迭代器失效的问题。

⭐️ 文档中写道,函数调用后会返回刚删除位置的下一个位置,即这个意义已经改变的pos (An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.)

❤️ 终版insert,添加返回值

    iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);assert(_start < _finish);

        iterator begin = pos +1;while(begin < _finish){*(begin -1)=*begin;
            begin++;}
        _finish--;return pos;}

删除v中所有偶数的正确代码 ——

//测试erase - 迭代器失效问题voidtestVector5(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);//v.push_back(5);

        vector<int>::iterator it = v.begin();while(it != v.end()){if(*it %2==0){
                v.erase(it);}else{
                it++;}}for(auto e : v){
            cout << e <<" ";}
        cout << endl;}

5.3 小总结

  1. 迭代器失效主要发生在insert和erase中,用了迭代器改变了底层的数据结构。迭代器失效了,就不要再去访问;若还需要访问,可先接收返回值更新。
  2. 只要是使用迭代器访问的容器,都可能涉及迭代器失效问题。string类同样也会失效,什么时候失效参考vector即可(但是一般很少涉及迭代器失效,因为string的插入删除不太用迭代器,常用的是下标)。

附:vector.h & vector.cpp

这里要特别说明一下testVector6,是为了测试vector中是自定义类型的情况,比如string。这涉及深浅拷贝问题,reserve时若用memcpy就是浅拷贝,会有问题,就需要一个一个对象进行深拷贝赋值

如果用memcpy,会出现长字符串是乱码,短字符串能正常打印的情况。

这是因为vs做了一些优化, 如果字符串较短(16),不会去堆上申请空间了,直接在对象中在栈上放一个小数组,用空间换取时间,所以幸存了。在Linux运行,全部都是乱码 ——

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cfpl92zi-1647934598043)(C:\Users\13136\AppData\Roaming\Typora\typora-user-images\image-20220322152758321.png)]

#include<iostream>#include<assert.h>#include<algorithm>#include<string>usingnamespace std;namespace beatles
{template<classT>classvector{public:typedef T* iterator;typedefconst T* const_iterator;//1.无参构造vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr){}//2.迭代器区间构造template<classInputIterator>vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){while(first != last){push_back(*first);
                first++;}}// 拷贝构造1 - 传统写法// 注:这里的memcpy同样是有问题的,要和reserve一样一个一个拷贝//vector(const vector<T>& v)//{//    _start = new T[v.capacity()];//    _finish = _start + v.size();//    _endofstorage = _start + v.capacity();//    memcpy(_start, v._start, sizeof(T)*v.size());//}voidswap(vector<T>& v){
            std::swap(_start,v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstorage, v._endofstorage);}// 拷贝构造2 - 现代写法 - 借助构造vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_endofstorage(nullptr){
            vector<T>tmp(v.begin(), v.end());swap(tmp);/*swap(_start, tmp._start);
            swap(_finish,tmp._finish);
            swap(_endofstorage, tmp._endofstorage);*/}// 赋值重载
        vector<T>&operator=(vector<T> v){swap(v);return*this;}~vector(){if(_start){delete[] _start;
                _start = _finish = _endofstorage =nullptr;}}

        size_t size()const{return _finish - _start;}

        size_t capacity()const{return _endofstorage - _start;}

        iterator begin(){return _start;}

        iterator end(){return _finish;}

        const_iterator begin()const{return _start;}

        const_iterator end()const{return _finish;}

        T&operator[](size_t n){assert(n <=size());return _start[n];}const T&operator[](size_t n)const{assert(n <=size());return _start[n];}voidresize(size_t n,const T& val =T()){if(n <=size()){
                _finish = _start + n;}else{if(n >capacity()){reserve(n);}while(_finish != _start + n){*_finish = val;
                    _finish++;}}}voidreserve(size_t n){if(n >capacity()){
                T* tmp =new T[n];
                size_t sz =size();if(_start){/*memcpy(tmp, _start, sizeof(T)*sz);
                    delete[] _start;*/for(size_t i =0; i < sz; i++){
                        tmp[i]= _start[i];}delete[] _start;}
                _start = tmp;
                _finish = _start + sz;
                _endofstorage = _start + n;}}voidpush_back(const T& x){if(_finish == _endofstorage){reserve(capacity()==0?4:capacity()*2);}*_finish = x;
            _finish++;}voidpop_back(){assert(_finish > _start);
            _finish--;}

        iterator insert(iterator pos,const T& x){assert(pos >= _start);assert(pos <= _finish);if(_finish == _endofstorage){// 扩容会导致迭代器失效,需要提前计算更新pos
                size_t len = pos - _start;reserve(capacity()==0?4:capacity()*2);
                pos = _start + len;}

            iterator end = _finish -1;while(end >= pos){*(end +1)=*end;
                end--;}*pos = x;
            _finish++;return pos;}

        iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);assert(_start < _finish);

            iterator begin = pos +1;while(begin < _finish){*(begin -1)=*begin;
                begin++;}
            _finish--;return pos;}private:
        iterator _start;
        iterator _finish;
        iterator _endofstorage;};// 测试vector中的对象是自定义类型// 若按照memecpy这种错误写法会出现乱码voidtestVector6(){
        vector<string> v;
        v.push_back("1111111111111111111111");
        v.push_back("1111111111111111111111");
        v.push_back("1111111");
        v.push_back("1111111");
        v.push_back("1111111");for(auto& e : v){
            cout << e <<" ";}
        cout << endl;}//测试erase - 迭代器失效问题voidtestVector5(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);//v.push_back(3);
        v.push_back(4);
        v.push_back(5);//删除数组中的偶数//测试三种场景//1 2 3 4 5 -> 正常(巧合)//1 2 4 5    -> 没删利索//1 2 3 4    -> 崩了//vector<int>::iterator it = v.begin();//while (it != v.end())//{//    if (*it % 2 == 0)//    {//        v.erase(it);//    }//    it++;//}

        vector<int>::iterator it = v.begin();while(it != v.end()){if(*it %2==0){
                v.erase(it);}else{
                it++;}}for(auto e : v){
            cout << e <<" ";}
        cout << endl;}//测试insert - 迭代器失效问题voidtestVector4(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);

        vector<int>::iterator it =find(v.begin(), v.end(),2);if(it != v.end()){
            it = v.insert(it,20);// 若insert时发生了扩容,会导致it指向的空间被释放// it本质上是野指针,这种问题,我们就叫做迭代器失效}
        v.insert(it,10);for(auto e : v){
            cout << e <<" ";}
        cout << endl;}// 测试拷贝构造&赋值重载,//同时测试了迭代器区间构造voidtestVector3(){
        vector<int> v1;
        v1.push_back(1);
        v1.push_back(2);
        v1.push_back(3);
        v1.push_back(4);
        v1.push_back(5);

        vector<int>v2(v1);for(auto e : v2){
            cout << e <<" ";}
        cout << endl;

        vector<int> v3;
        v3.push_back(10);
        v3.push_back(20);
        v3.push_back(30);
        v1 = v3;for(auto e : v1){
            cout << e <<" ";}
        cout << endl;}//测试resizevoidtestVector2(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);//1.
        v.resize(6,6);for(auto e : v){
            cout << e <<" ";}
        cout << endl;//2.
        v.resize(10);for(auto e : v){
            cout << e <<" ";}
        cout << endl;//3.
        v.resize(2);for(auto e : v){
            cout << e <<" ";}
        cout << endl;}//测试尾插尾删,同时测试reserve//遍历测试 [] & 迭代器voidtestVector1(){
        vector<int> v;
        v.push_back(1);
        v.push_back(2);
        v.push_back(3);
        v.push_back(4);
        v.push_back(5);
        v.push_back(6);
        v.pop_back();for(size_t i =0; i < v.size(); i++){
            cout << v[i]<<" ";}
        cout << endl;

        vector<int>::iterator it = v.begin();while(it != v.end()){
            cout <<*it <<" ";
            it++;}
        cout << endl;for(auto e : v){
            cout << e <<" ";}
        cout << endl;}}

vector.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include"vector.h"intmain(){//beatles::testVector1();//beatles::testVector2();//beatles::testVector3();//beatles::testVector4();//beatles::testVector5();
    beatles::testVector6();return0;}
标签: c++

本文转载自: https://blog.csdn.net/qq_54851255/article/details/123662474
版权归原作者 呀小边同学 所有, 如有侵权,请联系我们删除。

“【C++】vector的模拟实现@STL &mdash;&mdash; 迭代器失效问题”的评论:

还没有评论