C++ STL - vector 模拟实现+解析迭代器

目录

vector使用

vector模拟实现

vector实现解析:

memcpy进行元素拷贝问题:

扩容问题:

vector迭代器解析:

vector迭代器失效问题:

1.  示例一:一个典型的迭代器失效bug:insert实现

2. 示例二:insert引起的迭代器失效问题:

3. erase导致迭代器失效问题:

迭代器失效总结:

vector> 的理解:


vector使用

vector就是一个可变大小的数组,有多种构造函数。有析构函数,因为涉及内存管理,自然也有了拷贝构造和operator=。

大小相关:size获取数组大小,capacity获取当前可存储最大元素个数。主要是resize和reserve,resize改变数组大小,可变大可变小,扩size时可同时对元素初始化。reserve用于扩容,当知道vector大致存储元素个数时,可以用reserve缓解扩容带来的性能消耗。reserve的值小于当前capacity时,不会缩容。大于时会扩容,这里的容量不是size而是capacity。

我们知道,当vector的size==capacity时,再push_back会扩容,那么,vector的扩容机制是怎样的呢?实际上STL并没有规定vector的扩容机制,不同版本的STL有不同的存储策略。vs下capacity是按1.5倍增长的,g++是按2倍增长的。 这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

元素访问:支持operator[],这也是list,forward_list所不支持的。根本原因是底层存储为连续存储。back front at

修改:push_back pop_back,因为底层存储问题,push_front 和 pop_front时间复杂度较高,所以不支持头删头插,这也导致vector不适合做queue的适配容器。而可以做stack的适配容器。还比较重要的就是insert 和 erase了,也没什么,重载了多个版本,适于不同情况下的insert和erase,且都有返回值。insert返回新插入的第一个元素处的迭代器。erase返回删除元素或元素序列后的第一个元素处的迭代器。

说了很多FH,看https://cplusplus.com/reference/vector/vector/即可。

vector模拟实现

#ifndef STL_VECTOR_H
#define STL_VECTOR_H
#include "reverse_iterator.h"

namespace yzl
{
    // 拷贝构造,重载赋值都没实现。
    template> // 就是一个用于申请T类型对象的类型而已。Alloc就是这么一个类型
    class vector {
    public:
        typedef T value_type;
        typedef value_type *iterator;
        typedef const value_type *const_iterator;

        typedef yzl::__reverse_iterator reverse_iterator;
        typedef yzl::__reverse_iterator const_reverse_iterator;
    private:
        iterator _start;
        iterator _finish;
        iterator _end_of_storage;
    public:
        iterator begin() {
            return _start;
        }

        iterator end() {
            return _finish;
        }

        const_iterator begin() const {
            return _start;
        }

        const_iterator end() const {
            return _finish;
        }

        const_iterator cbegin() const {
            return _start;
        }

        const_iterator cend() const {
            return _finish;
        }

        reverse_iterator rbegin()
        {
            return yzl::__reverse_iterator(end());
        }

        reverse_iterator rend()
        {
            return yzl::__reverse_iterator(begin());
        }

        yzl::__reverse_iterator rbegin() const
        {
            return yzl::__reverse_iterator(end());
        }

        const_reverse_iterator rend() const
        {
            return const_reverse_iterator(begin());
        }

        const_reverse_iterator crbegin() const
        {
            return const_reverse_iterator(end());
        }
        const_reverse_iterator crend() const
        {
            return const_reverse_iterator(begin());
        }
    public:
        vector()
                : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}

//        vector(size_t n, const T& val=T())
//        : _start(new T[n]), _finish(_start+n), _end_of_storage(_start+n)
//        {
//            for(size_t i = 0; i < n; ++i)
//                *(_start+i) = val;    // 调用T的赋值运算符
//        }
        // 必须有下面这个,如果是size_t  T的如上函数,则会出现报错。
        vector(int n, const T &value = T())
                : _start(new T[n]), _finish(_start + n), _end_of_storage(_finish) {
            for (int i = 0; i < n; ++i) {
                _start[i] = value;
            }
        }

        template
        vector(InputIterator first, InputIterator last)
                : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
//            size_t sz = last - first;
            while(first+sz != last)
            {
                ++sz;
            }
//            _start = new T[sz];
//            _finish = _start + sz;
//            _end_of_storage = _start + sz;
//            for(size_t i = 0; i < sz; ++i)
//            {
//                *(_start+i) = *first;
//                ++first;
//            }

            // 复用版本:  有问题:
            while (first != last) {
                this->push_back(*first);
                first++;
            }
        }

//        vector(const vector &v)
//                : _start(new T[v.size()]), _finish(_start + v.size()), _end_of_storage(_finish) {
//            // 不能用memcpy,memcpy是逐字节拷贝,若涉及深度拷贝则出错。
//            for (size_t i = 0; i < v.size(); ++i) {
//                *(_start + i) = *(v._start + i);
//            }
//        }
        // vector拷贝构造的第二种写法:
//        vector(const vector& v)
//        : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
//        {
//            reserve(v.size());
//            for(const auto& i : v)
//            {
//                this->push_back(i);
//            }
//        }

        // vector的现代写法:借用其他的构造
        vector(const vector &v)
                : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
            // 这是要基于迭代器区间构造
            vector tmp(v.begin(), v.end());
            this->swap(tmp);
        }

        vector &operator=(vector v) {
            this->swap(v);
            return *this;
        }

        ~vector() {
            if (_start) {
                delete[] _start;
                _start = _finish = _end_of_storage = nullptr;
            }
        }
        // -------------------------------------------------------------------
        size_t size() const {
            return _finish - _start;
        }

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

        void reserve(size_t n) {
            if (n > capacity()) {
                size_t oldSize = size();
                T *tmp = new T[n];
                // 不能memcpy,必须这样,防止元素类型涉及深拷贝。
                for (size_t i = 0; i < size(); ++i) {
                    *(tmp + i) = *(_start + i);
                }
                delete[] _start;
                _start = tmp;
                _finish = _start + oldSize;
                _end_of_storage = _start + n;
            }
        }

        void resize(size_t n, const T &val = T()) {
            if (n > size()) {
                reserve(n);
                size_t sz = size();
                for (; sz < n; ++sz) {
                    *(_start + sz) = val;
                }
                _finish = _start + n;
            } else {
                _finish = _start + n;
            }
        }

        void push_back(const T &val) {
            if (_finish == _end_of_storage) {
                reserve(size() == 0 ? 4 : 2 * size());
            }
            *(_start + size()) = val;
            ++_finish;
        }

        void pop_back() {
            assert(size() > 0);
            --_finish;
        }

        iterator insert(const_iterator position, const value_type &val) {
            assert(position >= _start);
            assert(position <= _finish);
            if (_finish == _end_of_storage) {
                size_t diff = position - _start;
                reserve(size() == 0 ? 4 : 2 * size());
                position = _start + diff;   // 纠正迭代器,因为扩容之后原迭代器就失效了   insert引起的迭代器失效!!!!!
            }
            iterator end = _finish - 1;
            while (end >= position) {
                *(end + 1) = *(end);
                end--;
            }
            *(_start + (position - _start)) = val;   // 调用value_type的赋值操作。
            ++_finish;
            return _start + (position - _start);
        }

        iterator insert(const_iterator position, size_t n, const value_type &val) {
            assert(position >= _start);
            assert(position <= _finish);
            if (_finish + n > _end_of_storage) {
                size_t diff = position - _start;
                reserve(size() + n);
                position = _start + diff;
            }
            iterator it = _finish - 1;
            while (it >= position) {
                *(it + n) = *it;
                --it;
            }
            size_t diff = position - _start;
            for (size_t i = diff; i < diff + n; ++i) {
                *(_start + i) = val;
            }
            _finish += n;
            return _start + diff;
        }

        iterator erase(const_iterator pos) {
            assert(pos >= _start && pos < _finish);
            iterator it = _start + (pos - _start);
            while (it != _finish - 1) {
                *it = *(it + 1);
                ++it;
            }
            --_finish;
            return _start + (pos - _start);
        }

//        iterator erase(const_iterator first, const_iterator lase)
//        {
//            return first;
//        }
        T &operator[](size_t n) {
            assert(n < size());
            return *(_start + n);
        }

        const T &operator[](size_t n) const {
            assert(n < size());
            return *(_start + n);
        }

        bool empty() const {
            return _start == _finish;
        }

        T &front() {
            assert(size() > 0);
            return *_start;
        }

        const T &front() const {
            assert(size() > 0);
            return *_start;
        }

        T &back() {
            assert(size() > 0);
            return *(_finish - 1);
        }

        const T &back() const {
            assert(size() > 0);
            return *(_finish - 1);
        }

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

        void clear() {
            _finish = _start;
        }
    };
}


#endif //STL_VECTOR_H

vector实现解析:

1. vector就是靠三个元素指针T*维护的,_start    _finish    _end_of_storage。C++ STL - vector 模拟实现+解析迭代器_第1张图片所以,每个vector对象中只存储三个指针变量T*,这些指针指向的区域存储着具体的元素。push_back reserve insert erase等操作都是基于这三个指针进行操作。

memcpy进行元素拷贝问题:

我们知道,当_finish == _end_of_storage时,表示当前没有多余空间,若进行push_back,则需要扩容,扩容并非简单的原地扩容,而是要经历申请新空间->拷贝元素->释放旧空间的过程。
拷贝元素时,并不能一概使用memcpy进行拷贝,memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。如果拷贝的是简单的内置类型int等,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。

        void reserve(size_t n) {
            if (n > capacity()) {
                size_t oldSize = size();
                T *tmp = new T[n];
                // 不能memcpy,必须这样,防止元素类型涉及深拷贝。
                for (size_t i = 0; i < size(); ++i) {
                    *(tmp + i) = *(_start + i);
                }
                delete[] _start;
                _start = tmp;
                _finish = _start + oldSize;
                _end_of_storage = _start + n;
            }
        }

扩容问题:

韩信带净化,无多余可用空间时,扩容机制并非一律以2倍或1.5倍扩容,而是要看具体STL实现版本。

但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

vector迭代器解析:

因为vector底层实现是在一段连续的内存中存储,所以,vector的迭代器就是原生指针。

        typedef T value_type;
        typedef value_type *iterator;
        typedef const value_type *const_iterator;

        typedef yzl::__reverse_iterator reverse_iterator;
        typedef yzl::__reverse_iterator const_reverse_iterator;

iterator 即 T*    const_iterator 即 const T*  

那么,这样可行的原因是什么呢?实际上,我们要看迭代器需要哪些操作:解引用,++,--,==,!=。那么T*完全具有这些操作。并且const T*也完全符合const迭代器的要求,即返回值为const的,且迭代器本身可++,--。

vector迭代器失效问题:

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

那么,那些可能使原生态指针指向空间销毁的情况,也是我们需要注意迭代器失效的情况。

1.  示例一:一个典型的迭代器失效bug:insert实现

        iterator insert(const_iterator position, const value_type &val) {
            assert(position >= _start);
            assert(position <= _finish);
            if (_finish == _end_of_storage) {
                size_t diff = position - _start;
                reserve(size() == 0 ? 4 : 2 * size());
                position = _start + diff;   // 纠正迭代器,因为扩容之后原迭代器就失效了   insert引起的迭代器失效!!!!!
            }
            iterator end = _finish - 1;
            while (end >= position) {
                *(end + 1) = *(end);
                end--;
            }
            *(_start + (position - _start)) = val;   // 调用value_type的赋值操作。
            ++_finish;
            return _start + (position - _start);
        }

如上代码中,如果size == capacity,则需要reserve,在reserve之前,存储diff,是因为一旦reserve了,则原来的_start _finish _end_of_storage 包括上方的position就都失效了。这时,对position重新赋值,就可以解决此bug。

2. 示例二:insert引起的迭代器失效问题:

void test4()
{
    yzl::vector v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
//    v.push_back(5);

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;

    auto p = find(v.begin(), v.end(), 3);
    if (p != v.end())
    {
        // 在p位置插入数据以后,不要访问p,因为p可能失效了。
        v.insert(p, 30);

//        cout << *p << endl;
        v.insert(p, 40);
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}
void test4()
{
    yzl::vector v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;

    auto p = find(v.begin(), v.end(), 3);
    if (p != v.end())
    {
        // 在p位置插入数据以后,不要访问p,因为p可能失效了。
        v.insert(p, 30);

        cout << *p << endl;
        v.insert(p, 40);
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
}

第一段test4代码中,push_back了4次,此时已满,第一次insert时,是需要reserve的,导致vector的整个存储空间都发生了改变,原来空间上的指针都失效了,而p迭代器就是原生指针,导致此时再进行第二次insert时,就会发生报错,因为访问了非法空间。

而第二段test4中,push_back5次,此时size==5 capacity == 8,在进行两次insert时,不会发生扩容,也就使得p所指向空间还是有效的,程序不会发生报错。并且cout<<*p<

为了解决上述问题,insert函数本身具有迭代器返回值,返回的迭代器指向新插入的第一个元素。所以,为了避免insert带来的迭代器失效问题,我们应该

pos = v.insert(pos, 3);

3. erase导致迭代器失效问题:

a、不排除某些STL实现的erase在size

b、 

void test5()
{
    yzl::vector v;
    v.push_back(1);
    v.push_back(2);
//    v.push_back(4);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    auto it = v.begin();
    //   error
//    while(it != v.end())
//    {
//        if(*it %2==0)
//            v.erase(it);
//        it++;
//    }
    while(it != v.end())
    {
        if(*it % 2 == 0)
        {
            it = v.erase(it);
        }
        else
        {
            ++it;
        }
    }
    for(auto&i:v)
        cout<

error为错误的erase使用,后方为正确的。

我们会发现,随着v的内容不同,基于错误的erase使用,结果不同。12345 1234 124345 会有不同的结果。

比如1234  删除2 ++ 指向4 删除4 ++ 越界。导致程序崩溃。
比如124345 删除2 ++ 指向3 ++ 删除4 ++ 等于end,程序不崩溃,但是没有删除第一个4

其实很简单,就是erase的错误使用。所以,erase有返回值,我们需要正确使用erase

c、erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代 器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了。

不同IDE使用的不同版本的STL,对于erase操作的管控程度不同,但是根本上来说,erase之后,都不应该再访问原来的迭代器,而必须更新迭代器。

4. 与vector类似,string在经历扩容后,原来的迭代器也会失效。

迭代器失效总结:

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

迭代器失效解决办法:在使用前,对迭代器重新赋值即可。insert和erase因此也有了返回值。

vector> 的理解:

C++ STL - vector 模拟实现+解析迭代器_第2张图片

C++ STL - vector 模拟实现+解析迭代器_第3张图片

 上面两个图就足够清楚了。

你可能感兴趣的:(C/C++学习,c++,开发语言)