【c++】模拟实现vector

一.vector的成员变量与迭代器

vector里面可以存各种数据类型,所以必定会用到模板,假设vector的模板参数为T,那么T*这个指针封装后就是vector的迭代器

和string的迭代器很像,只不过string确定存的是字符,所以迭代器直接是char的指针,而vector存的数据类型不确定,就使用T的指针

而vector的3个成员变量_start,_finish,_endofstorage也就是3个迭代器,分别指向vector的开头,vector有效数据的结尾,vector容量的结尾

二.主干结构

namespace cyf
{
    
    template<class T>
    class vector
    {
        typedef T* iterator;
    public:
        //...
    private:
        //迭代器可看作一个指针,但也有区别
        iterator _start;//起始位置
        iterator _finish;//最后一个有效元素的下一位置
        iterator _endofstorage;//最大容量位置下一位
    };
}

三.构造函数和析构函数

1.构造函数

// 1
vector(size_t n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
        resize(n,val);
}
// 用 n 个 val 值进行初始化,可以复用 resize 接口,但要注意调用resize()接口需要计算size()和capacity(),所以要将成员变量进行初始化

// 2
template<class InputIterator>
vector(InputIterator first, InputIterator last)
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}
//   用一段迭代器区间进行初始化,由于不同类型的容器迭代器类型可能不同,因此设计成函数模板,将区间内的内容尾插入vector即可,但注意调用push_back接口通过 _finish == _endofstorage 判断是否满,需要初始化


// 3
vector(size_t n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    reserve(n);
    for (size_t i = 0;i < n;++i)
    {
        push_back(val);
    }
}


// 4
vector(int n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    reserve(n);
    for (size_t i = 0;i < n;++i)
    {
        push_back(val);
    }
}

构造函数3和4的原因是当 vector<int> v(10,5)去初始化时 这是因为当使用两个参数进行构造时,编译器会优先匹配构造函数 (2),10和5都是int 类型但是1中对应的是size_t和int类型,所以编译器会优先匹配构造函数 2 ,此时对*first解引用会报错。所以需要再实现两个不同参数类型的构造函数重载:

2.析构函数

        ~vector()
        {
            if (_start)
            {
                delete[] _start;//释放空间
            }
            _start = _finish = _end_of_storage = nullptr;//置空
        }

四.reserve和resize

   void reserve(size_t n)
{
    if (n > capacity())
    {
        T* tmp = new T[n]; // 新开辟一块空间
        // 容器不为空
        if (_start)
        {
            size_t sizecp = size(); // 计算原来容器size个数
            for (size_t i = 0; i < size(); i++) // 拷贝数据
            {
                tmp[i] = _start[i];
            }
            delete[]_start; // 释放旧空间

            _start = tmp; // 更新_start
            _finish = _start + sizecp; // 更新_finish
            _endofstorage = _start + n; // 更新_endofstorage
        }
        // 容器为空,更新_start,_finish,_endofstorage的位置
        else
        {
            _start = _finish = tmp;
            _endofstorage = _start + n;
        }
    }
}

在reserve模拟实现时最容易犯的错误就是用memcpy进行拷贝数据 ,因为对于需要深拷贝的自定义类型,使用memcpy函数以后,新开辟空间里的元素和原空间里的元素所指向的内存空间是一样的,但是对于自定义类型来说扩容就需要深拷贝,memcpy拷贝完成后释放旧空间,当旧空间被释放时,会调用自定义类型的析构函数,从而使得新开辟空间里的元素指向的内存空间也被释放掉了,此时新空间中_start指向的就是被释放的空间,vector使用完后会自动调用析构函数,将_start指向的空间置为nullptr,将不属于自己维护的空间置为空释放就会报错。

void resize(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++;
            }
        }
    }

当n<size时直接将_finish移动到_start+n的位置但是容量是不变的

当n>size并且大于capacity时先扩容然后填充数据

五.拷贝构造与operator=

        vector(const vector<T>& v)
            :_start(nullptr)
            , _finish(nullptr)
            , _endofstorage(nullptr)
        {
            reserve(v.capacity()); //直接push_back的话capacity可能不一样大
            for (auto e : v)
            {
                push_back(e);
            }
            
        }
  • 进行拷贝构造时我们也容易使用memcpy进行拷贝,也会造成和reserve一样的问题

  • 所以我们可以用另一种巧妙的方式避免浅拷贝,那就是先扩容然后遍历要拷贝的vector,将其中每一个数据push_back进新的vector中

        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstorage, v._endofstorage);
        }
        vector<T>& operator=(vector<T> v)
        {
            swap(v);
            return *this;
        }

六.插入和删除

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

尾插首先检查容量,如果没有容量就扩容,在有效元素的下一个位置插入数据,并且挪动_finish的位置

void pop_back()
        {
            assert(!empty());
            _finish--;
        }

尾删要保证有元素才能进行删除,只需要挪动_finish 的位置即可

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

在pos位置插入数据x 首先检查pos位置的合理性,然后检查容量,从后往前挪动数据在pos 位置插入数据然后++_finish

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

覆盖性删除元素注意边界的问题

七.其他接口

iterator begin()
{
    return _start;
}
iterator end()
{
    return _finish;
}
const_iterator begin()
{
    return _start;
}
const_iterator end()
{
    return _finish;
}


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


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

八.迭代器失效问题

    • 扩容导致迭代器失效

#include <iostream>
#include <vector>
using namespace std;

void Test()
{
    vector<int> v{ 1,2,3,4,5,6 };
    auto it = v.begin();

    // 将有效元素个数增加到100个,多出的位置使用1填充,操作期间底层会扩容
    // v.resize(100, 1);
    
    // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
    // v.reserve(100);
    
    // 插入元素期间,可能会引起扩容,而导致原空间被释放
    // v.insert(v.begin(), 0); // v.push_back(8);
    
    // 给vector重新赋值,可能会引起底层容量改变
    v.assign(100, 8);

    while (it != v.end()) 
    {
        cout << *it << " ";
         ++it;
    }
    cout << endl;
}

int main()
{
    Test();
    return 0;
}

出错原因: 以上操作,都有可能会导致vector扩容,也就是说vector底层原来旧空间被释放掉, 而在打印时,it还使用的是释放之前的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间而引起代码运行时崩溃。

解决方式: 在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可

2.erase导致迭代器失效

#include <iostream>
#include <vector>
using namespace std;

void Test()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
    // 使用find查找3所在位置的iterator
    auto pos = find(v.begin(), v.end(), 3);
    // 删除pos位置的数据,导致pos迭代器失效。
    v.erase(pos);
    cout << *pos << endl; // 此处会导致非法访问
}

int main()
{
    Test();
    return 0;
}

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

总之,使用迭代器应注意迭代器失效问题,解决办法是:在使用前,对迭代器重新赋值即可

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bite-ccc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值