C++--vector的模拟实现--迭代器失效--深浅拷贝问题--0917(已修改)

 1.vector的初步模拟实现

下面是完整的模拟实现,改进会在后文中提及。

#pragma once
#include <assert.h>
namespace chy
{
    template<class T>
    class vector
    {
         
     public:
        typedef T* iterator;//必须共有 要不然迭代器用不了
        typedef const T* const_iterator;//常量迭代器某些场景需要
        //普通迭代器
        iterator begin()
        {
            return _start;
        }
        iterator end()
        {
            return _finish;
        }
        //const_iterator常量迭代器
        const_iterator begin() const
        {    
            return _start;
        }
        const_iterator end() const
        {    
            return _finish;
        }
        
        //构造
        vector()
            :_start(nullptr);
            ,_finish(nullptr);
            ,_end_of_storage(nullptr);
        {}
        ~vector()
        {
            delete[] _start;
            _start=_finish=_end_of_storage=nullptr;
        }
        
        const T& operator[](size_t pos)
        {
            assert(pos<size());
            return _start[pos];
        }
        size_t capacity() const
        {
            return _end_of_storage -_start;
        }
        size_t size() const
        {
            return _finish- _start
        }
        
        //这里有深浅拷贝问题 最终版在下文
        void reserve(size_t n)
        {
            if(n>capacity())
            {    
                size_t sz=size();//保留size的大小
                T*tmp=new T[n];
                if(_start)
                {    //_start不为空 把之前的数据拷贝过来
                    memcpy(tmp,_start,sizeof(T)*size())
                    delete[] _start;
                }
                
                _start=tmp;
                _finsh=_start+sz;// _start+size(); 这里算size() _finish-size()抵消了
                _end_of_storage=_start+n;
            }
        }
        
        void push_back(const T& x)
        {
            //不加& vector<string> v; v.push_back("xxxx");编不过
            //加const 可以支持隐式类型转换
            if(_finsh==_end_of_storage)
            {
                reserve(capacity()==0?4:capacity()*2)
            }
            *_finish=x;
            _finish++;
        }
        //尾插可以复用insert
        void push_back2()
        {
            insert(end(),x);
        }
        
        void pop_back()
        {    
            assert(_finish>_start);
            --_finish;
        }

    
        void insert(iterator pos,const T& x)//这里有迭代器失效问题 更新版在下面标题
        {                                   //插入前一个赋后一个
            assert(pos>=_start);
            assert(pos<=_finish);
            if(_finish==_end_of_storage)
            {
                reserve(capacity()==0?4:capacity()*2);
            }
            iterator end=_finish-1;//_finish的类型
            while(end>=pos)
            {
                *(end+1)=*end;
                end--;
            }
            *pos=x;
            ++_finish;
        }
                         //存在问题 改进版见下一标题处
        void erase(iterator pos)//删除从pos位置的下一个开始 后一个赋前一个
        {
            assert(pos>=_start);
            assert(pos<_finish);
            iterator begin=pos+1;
            while(begin<_finish)
            {
                *(begin-1)=*(begin);
                begin++;
            }  
            --_finish; 
            //if(size()<capacity()/2)
            //{
            //  缩容 如果支持这个功能 也存在迭代器失效问题
            //}
        }
        //总结  插入前赋后-- 消除后赋前++ 
                   
     private:
        iterator _start;//开始
        iterator _finish;//数据结束
        iterator _end_of_storage;//空间结束
     };
        
    
}
void test1()
{
    vector<int>v;
    v.push_back(3);
    v.push_back(1);
    v.push_back(9);
    v.push_back(6);
    v.push_back(7);
    vector<int>::iterator it=v.begin();
    while(it!=v.end())
    {
        cout<<*it<<" ";
        ++it; 
    }
}

//常量迭代器应用场景
void Func(const vector<int>& v)
{
    vector<int>::const_iterator it=v.begin();//这里v是const修饰的 如果不调用常量迭代器
                                                //会出现权限放大的问题
    while(it!=v.end())
    {
        cout<<*it<<" ";
        ++it;
    }

}

1.2vector的构造函数的补充

拷贝构造

vector(size_t n,const T& val=T() )//用n个数据去构造 数据没给就去调用
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
                                  //用T的匿名对象 去调用对应类型的默认构造
{
    reserve(n);//开n个空间
    for(int i=0,i<n;i++)
    {
        push_back(val);//插入n个val
    }
}

 使用迭代器进行构造

 现代写法(找一个打工仔)的实现需要一个有参数的构造函数,根据vector的构造函数,下面实现由迭代器实现的

 这里使用模板的原因是可以适配不同容器的迭代器,只需要迭代器解引用的值在模板即可。

template <class InputIterator>
vector(InputIterator frist,InputIterator last)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
{
    while(frist)
    {
        push_back(*frist);
        ++frist;
    }

}

1.2.2 出现的问题

int main()
{
    vector<int> v1(8);//这里调用的是上面写的构造
    // 0 0 0 0 0 0 0 0
    vector<int*> v2(8);//同样
    // 00000000 00000000 00000000 00000000 0000000 00000000 ...
    vector<int> v3(8,1);//这里报错
    return 0;
}

且报错在用迭代器构造的地方

1.3 拷贝构造的多种写法

常规写法

//v2(v1) 这里有深浅拷贝问题 最终版在下文
vector(const vector<T>& v)
{
    _start=new T[v.size()];
    memcpy(_start,v._start,sizeof(T)*v.size());
    _finish=_start+v.size();
    _end_of_storage=_start+v.size()
}

//第二种
vector(const vector<T>& v)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
{
    for(auto e:v)
    {
        push_back(v);
    }

}

 基于迭代器构造的现代写法

void swap(vector<T>& v)
{
    std::swap(_start,v._start);
    std::swap(_finish,v._finish);
    std::swap(_end_of_storage,v._end_of_storage);
}

vector(const vector<T>& v)
    :_start(nullptr)
    ,_finish(nullptr)
    ,_end_of_storage(nullptr)
{
    vector<T> tmp(v.begin(),v.end())
    swap(tmp);
}

 1.4 赋值运算符重载

//v1=v2
vector<T>& operator=(vector<T> v)
{
    swap(v);
    return *this;
}

这里没带引用 v是v2的临时拷贝 用于交换


2、vector迭代器失效问题

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

可能导致迭代器失效的操作有:

1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等

2. 指定位置元素的删除操作--erase

3. 注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,尽管迭代器失效导致结果错误,程序仍然可以运行。
4. 与vector类似,string在插入+扩容操作+erase之后,迭代器也会失效。


2.1 插入时的迭代器失效

#include <algorithm>//算法 里面包含find
void test3()
{
    vector<int>v;
    v.push_back(2);
    v.push_back(3);
    v.push_back(1);
    v.push_back(9);
    vector<int>::iterator it=v.begin();
    while(it!=v.end())
    {
        cout<<*it<<" ";
        ++it; 
    }
    auto p=find(v.begin(),v.end(),3);//该函数找不到返回区间的末位置
    if(p!=v.end())
    {
        v.insert(p.30);
    }
}

这里出现了野指针问题,原因是:四个数据时再插入需要扩容,而扩容是在一个新的空间并释放旧空间,pos依旧指向原来的的空间。

2.1.2 insert的改良

 void insert(iterator pos,const T& x)
        {
            assert(pos>=_start);
            assert(pos<=_finish);
            if(_finish==_end_of_storage)
            {
                size_t len=pos-_start;
                reserve(capacity()==0?4:capacity()*2);
                //更新pos
                pos=_start+len;
            }
            iterator end=_finish-1;//_finish的类型
            while(end>=pos)
            {
                *(end+1)=*end;
                end--;
            }
            *pos=x;
            ++_finish;
        }

2.2 删除时的迭代器失效问题

要求删除所有偶数

void test4()
{    
    vector<int>v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    auto it=v.begin();
    while(it!=v.end())
    {
        if(*it%2==0)
        {
            v.erase(it);
        }
        ++it;
    }

原因是出现偶数时删除数据只是数据的移动,而指针依然指向原位置,此时再++会跳过数据,甚至因为earse中_finish--而导致it指针错过_finish而越界

void test4()
{    
    vector<int>v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    auto it=v.begin();
    while(it!=v.end())
    {
        if(*it%2==0)
        {
            v.erase(it);
        }
        else
        {
            ++it;
        }
    }
}

 2.2.2 erase的改良

//stl规定 earse返回删除位置下一个位置迭代器
iterator erase(iterator pos)
{
    assert(pos>=_start);
    assert(pos<_finish);
    iterator begin=pos+1;
    while(begin<_finish)
    {
        *(begin-1)=*(begin);
        begin++;
    }  
    --_finish; 
    return pos;       
}

 3、深浅拷贝问题

如果使用下面方法模拟实现中的拷贝构造

//拷贝构造 v2(v1)
vector(const vector<T>& v)
{
    _start=new T[v.size()];
    memcpy(_start,v._start,sizeof(T)*v.size());
    _finish=_start+v.size();
    _end_of_storage=_start+v.size();
}

 当使用类型为vector<vector<int>>时,memcpy是逐字节拷贝,会使二维数组中的一维数组vector<int>变为浅拷贝。

即T对象是自定义类型时。

仅有vector<vector<int>>为深层拷贝,其中_start中的vector<int>类型依旧为浅拷贝。在使用时,依旧会出现析构两次,且修改其中一个,另一个同时改变的问题。

3.2 在拷贝构造时解决深浅拷贝

如果使用的是老式方法,即直接调用拷贝构造,那么需要改变的是拷贝构造。

这里使用赋值产生的中间变量以解决上述问题。

vector(const vector<T>& v)
{
    _start=new T[v.size()];
    //memcpy(_start,v._start,sizeof(T)*v.size());
    
    for(size_t i=0;i<v.size();i++)
    {
        _start[i]=v._start[i];
    }    


    _finish=_start+v.size();
    _end_of_storage=_start+v.size();

}

3.3 使用迭代器先构造对象从而解决深浅拷贝

如果使用现代写法,即先利用迭代器实现构造,再进行交换实现的拷贝构造方法。由于迭代器构造使用了push_back(),在该函数中涉及reserve()函数,扩容是涉及深浅拷贝的。所以需要改变的就是reserve()函数。

与上述问题相同,有memcpy的地方就是浅拷贝。

解决方案

同样采用赋值方式。

void reserve(size_t n)
{
    if(n>_capacity)
    {
        size_t sz=size();
        T*tmp=new T[n];
        if(_start)
        {
            for(size_t i=0;i<sz;i++)
            {
                tmp[i]=_start[i];
            }
            delete[] _start;
        }
    _start=tmp;
    _finish=_start+sze;//扩容没改变大小
    _end_of_storage=_start+n;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值