vector的迭代器失效、深拷贝和模拟实现


vector的迭代器失效、深拷贝、模拟实现

迭代器失效

vector容器支持插入和删除操作.

insert:插入的成员函数原型是iterator insert (iterator position, const value_type& val);给定一个位置的迭代器,在该位置插入一个元素,返回一个新的迭代器,指向插入的那个元素。

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

vector<int> v{1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
it=v.insert(it, 9);
for (auto e : v)
    cout << e << ' ';
cout << endl;
cout << "返回的迭代器指向" << *it << endl;

运行结果:

1 9 2 3 
返回的迭代器指向9

在insert的过程中可能会出现迭代器失效,指的是在进行insert操作的时候vector如果容量不够会发生扩容。vector的扩容有3个步骤:

  1. 开辟一块新的空间
  2. 将原来空间的内容拷贝到新空间
  3. 在新空间进行插入
  4. 释放旧空间

迭代器失效的原因是当开辟了新空间以后,原来使用的迭代器依然指向旧空间,此时如果在使用该迭代器就是野指针。

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

vector<int> v{1, 2, 3};
v.reserve(3);
vector<int>::iterator it = v.begin() + 1;
v.insert(it, 9);//此时进行了扩容,将原数据拷贝到新空间
for (auto e : v)
    cout << e << ' ';
cout << endl;
v.insert(it, 9);//it已经是野指针,迭代器失效
for (auto e : v)
    cout << e << ' ';
cout << endl;

运行结果:

1 9 2 3 
0 1 9 2 3 
*** Error in `./mybin': double free or corruption (out): 0x0000000001da2c40 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81329)[0x7f6f6cb30329]

由于在进行insert操作时扩容会导致迭代器失效,所以在使用迭代器it以后就不要在使用了,如果想要多次插入,正确的做法接收insert函数的返回值,不是一直使用it。虽然在进行insert的时候it可能不会失效(没有发生扩容),可以继续使用,但是不推荐这种做法,不安全。

erase:删除的成员函数原型是iterator erase (iterator position);,给定一个位置的迭代器,删除该位置的元素,返回值是指向删除位置的迭代器。

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

vector<int> v{1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
it = v.erase(it);
for (auto e : v)
    cout << e << ' ';
cout << "删除元素以后返回的迭代器指向" << *it << endl;

运行结果:

1 3 删除元素以后返回的迭代器指向3

理论上而言,erase是删除元素,不会存在扩容,也就没有迭代器失效的问题,然而实际上在不同的平台下对于erase有不同的规定,在windows平台下,进行erase了以后会对it做强制检查,禁止再次直接使用it.在Linux平台是可以再次直接使用it的。

vector<int> v{1, 2, 3};
vector<int>::iterator it = v.begin() + 1;
v.erase(it);
for (auto e : v)
    cout << e << ' ';
cout<<endl;
v.erase(it);//在vs2022下,不允许在直接使用it(可以用it接收第一个erase的返回结果,然后使用)
for (auto e : v)
    cout << e << ' ';
cout<<endl;

上面这段程序在windows下是报错的,在Linux下可以通过,结果是:

1 3 
1 

在有的版本的STL中,vector进行erase可能会有缩容的操作,以时间换空间。这种情况下会出现迭代器失效的问题。所以进行erase以后也不要在直接使用it了,正确的做法是接收erase的返回值。

深拷贝

由于vector内部在堆区申请了空间,所以在进行拷贝构造和赋值运算符重载时,需要进行深拷贝,否则会出现不同对象共用同一块空间的问题。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b79uedHJ-1664030921600)(C:\Users\19199\AppData\Roaming\Typora\typora-user-images\image-20220924215059942.png)]

深拷贝的做法是在堆区重新为v2开辟一块空间,并让v2的指针指向重新开辟的那块空间[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w5DyW5Uo-1664030921601)(C:\Users\19199\AppData\Roaming\Typora\typora-user-images\image-20220924215456351.png)]

vector拷贝构造函数的写法

template<typename T>
class vector
{
public:
    //......
    vector(vector<T>& v)
    {
        _start=new T[v.capacity()];//开空间
        _finish=_start+v.size();
        _end_of_storage=_start+v.capacity();
        for(int i=0;i<v.size();i++)//拷贝原数据
        {
            *(_start+i)=v[i];
        }
    }
private:
    _start;
    _finish;
    _end_of_storage;
};

vector的拷贝构造函数中,拷贝原数据不能使用memcpy,因为vector是一个模板类,T可能是一个自定义类型,而使用memcpy只会把原数据进行简单的值拷贝,可能引发更深层次的拷贝的问题。例如当T是一个vector<int>的时候,就会引发更深层次的拷贝问题。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9Fz7hte-1664030921602)(C:\Users\19199\AppData\Roaming\Typora\typora-user-images\image-20220924222756836.png)]

如果T是一个vector<int>类型,进行memcpy的话只对第一层进行了深拷贝,对每一个元素vv[]是浅拷贝。不能解决更深层次的深拷贝问题,拷贝原数据的步骤应该写成for循环,这样的话在拷贝数据时,对于自定义类型,是调用它的赋值运算符重载,对于内置类型也能完成值拷贝的工作。可以实现更深层次的深拷贝

for(int i=0;i<v.size();i++)//拷贝原数据
{
    *(_start+i)=v[i];
}

模拟实现

vector是一个模板类,底层在物理空间上是连续的。

成员变量

template<typename T>
class vector
{
private:
    T* _start;//指向起始元素
    T* _finish;//指向结束元素的下一个元素
    T* _end_of_storage;//指向开辟空间的下一个位置
};

迭代器

typedef T *iterator;             // vector的迭代器是原生指针
typedef const T *const_iterator; // const迭代器

成员函数

//无参的默认构造函数
vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
//迭代器区间的构造函数
template <class Inputiterator> //使用模板的原因是可以支持不同类型的迭代器
vector(Inputiterator first, Inputiterator last)
    : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{
     while (first != last)
     {
         push_back(*first);
         first++;
     }
   }
//用n个对象进行构造
vector(size_t n, const T &val = T())
    : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
    {
        for (int i = 0; i < n; i++)
            push_back(val);
    }
//为了防止与使用模板的迭代器区间的构造函数混淆,将n个对象构造的构造函数写一个重载
vector(int n, const T &val = T())
    : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
    {
        for (int i = 0; i < n; i++)
            push_back(val);
    }
//析构函数
~vector()
{
    if (!_start)
        delete[] _start;
    _start = _finish = _end_of_storage = nullptr;
}
void reserve(size_t n)
{
    assert(n > _end_of_storage - _start); //确保真的在"扩容"
    T *tmp = new T[n];
    int count = _finish - _start;   // count表示原来空间的元素个数
    for (int i = 0; i < count; i++) //这里拷贝必须使用for循环,不能使用memcpy,因为T可能是自定义类型,需要深拷贝
        tmp[i] = _start[i];         //将原空间的内容拷贝到新空间
    delete[] _start;                //释放掉原来的空间
    _start = tmp;
    _finish = _start + count;
    _end_of_storage = _start + n; //更新_start,_finish,_end_of_storage
}
void push_back(const T &t) //使用引用,因为T可能是自定义类型
{
    if (_finish == _end_of_storage) //需要扩容
    {
        int newcapacity = _start == nullptr ? 4 : 2 * (_end_of_storage - _start);
        reserve(newcapacity); //扩容以后,_start,_finish,_end_of_storage已经变化
    }
    *_finish = t;
    _finish++;
}
size_t capacity() const //普通对象和const对象都能调用
{
    return _end_of_storage - _start;
}
size_t size() const
{
    return _finish - _start;
}
// v[2]
T& operator[](size_t n) //重载[]
{
    assert(n < this->size()); //断言
    return *(_start + n);
}
//要给const对象也提供[]
const T& operator[](size_t n)const
{
    assert(n< this->size());
    return *(_start+n);
}
bool empty() const
{
    return _start == _finish;
}
void pop_back() //删除尾部元素
{
    assert(!empty()); //不为空才能删除元素
    _finish--;        //不用真删除,_finish--即可
}
iterator insert(iterator pos, const T &val) //在迭代器位置进行插入元素
{
    if (_finish == _end_of_storage) //满了
        reserve((_end_of_storage - _start) * 2);
    iterator tmp = _finish;
    while (tmp > pos)
    {
        *tmp = *(tmp - 1); //后移
        tmp--;
    }
    *pos = val;
    _finish++;  //插入以后_finish++
    return pos; //返回指向新插入元素的迭代器
}
iterator begin() //普通对象的起始迭代器
{
    return _start;
}
iterator end() //普通对象的结束迭代器
{
    return _finish;
}
const_iterator begin() const // const对象的起始迭代器
{
    return _start;
}
const_iterator end() const // const对象的结束迭代器
{
    return _finish;
}
iterator erase(iterator pos)
{
    assert(!empty());
    iterator tmp = pos + 1;
    while (tmp != _finish) //将pos及其之后的元素前移
    {
        *(tmp - 1) = *tmp;
        tmp++;
    }
    _finish--;
    return pos;
}
// vector(const vector<T> &v) //拷贝构造函数,传统写法
// {
//     _start = new T[v.size()]; //这里new size个,防止空间浪费
//     for (int i = 0; i < v.size(); i++)//使用for循环,当T是自定义类型,调用赋值重载
//         _start[i] = v[i];
//     _finish = _end_of_storage = _start + v.size();
// }

//v2(v1),拷贝构造的现代写法
// vector(const vector<T> &v)
//     : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
// {
//     reserve(v.size());      //先给v2足够空间
//     for (const auto &e : v) //使用引用,T可能是自定义类型
//         push_back(e);       //将v1中的元素往v2插入
// }

//使用迭代器区间的构造函数实现拷贝构造函数的纯现代写法.v2(v1)
vector(const vector<T> &v)
    : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
    {
        vector<T> tmp(v.begin(), v.end()); //先根据迭代器区间构造一个tmp
        swap(tmp);                         //将this的成员变量和tmp交换,而且会调用tmp的析构,所以初始化列表要初始化
    }
void swap(vector<T> &tmp)
{
    std::swap(_start, tmp._start);
    std::swap(_finish, tmp._finish);
    std::swap(_end_of_storage, tmp._end_of_storage);
}
// v.resize(1)
void resize(size_t size, const T &val = T()) // resize复用reserve
{
    if (size <= this->size())
    {
        _finish = _start + size;
    }
    else if (size > this->size() && size <= capacity())
    {
        iterator cur = _finish;
        _finish = _start + size;
        while (cur != _finish)
        {
            *cur = val;
            cur++;
        }
    }
    else //需要扩容
    {
        reserve(size);
        _end_of_storage = _start + size;
        while (_finish != _end_of_storage)
        {
            *_finish = val;
            _finish++;
        }
    }
}
// 可以使用resize实现拷贝构造.v2(v1)
// vector(const vector<T> &v)
//     : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
// {
//     resize(v.size()); //此时v2中有size个默认生成的对象
//     for (size_t i = 0; i < v.size(); i++)
//         (*this)[i] = v[i]; //当T时自定义类型,调用赋值重载,赋值重载里面会对默认生成的对象进行处理
// }
// v1=v2,赋值运算符重载
vector<T> &operator=(vector<T> v) //这里要的就是传值,v是v2的拷贝
{
    swap(v); //交换以后,实现了v的属性到v1上,并且调用v的析构,清理v1的资源。
    //所以进行传值操作,如果传引用,交换以后v2会被修改。
    return *this;
}
T& front()//顺序表头部元素
{
    return *_start;
}
const T& front()const//顺序表头部元素
{
    return *_start;
}
T& back()//顺序表尾部元素
{
    return *(_finish-1);
}
const T& back()const //顺序表尾部元素
{
    return *(_finish-1);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值