• vector的简单模拟


     本编文章对 vector 进行简单的模拟实现,了解 vector 的基本原理,简单实现 vector

    目录

    vector 是什么?

    vector 的简单使用

    初始化

    三种遍历

    扩容--reserve

    扩容--resize

    缩容--shrink_to_fit

    insert--指定位置插入

    erase--指定位置删除

    find--查找

    sort--排序

    模拟实现

    基本成员

    迭代区间构造

    拷贝构造和赋值重载

    指定初始化的个数和参数

    begin 和 end

    size 和 capacity

    reserve--开辟空间不初始化

    解决野指针问题

    解决深浅拷贝问题

    insert--指定插入

    erase--指定删除

    尾插和尾删

    [] 重载


    vector 是什么?

    vector 是表示大小可以更改的数组的序列容器。我们理解为它相当于数组的封装。

    vector 的简单使用

    初始化

    vector 相当于类型名,而 vector 中存储的类型通常用 vector<类型> 来描述

    三种遍历

    下标、迭代器、范围for

    扩容--reserve

    reserve 提前开辟好空间,避免空间不足时频繁扩容,不会初始化

    扩容--resize

    提前开辟好空间,会自动初始化

    没有指定的初始化数据时,会根据数据的类型自动生成默认值:

    缩容--shrink_to_fit

    将容量调整到和size相等--少用慎用

    insert--指定位置插入

    insert(指定位置迭代器,插入的值)

    erase--指定位置删除

    find--查找

    查找指定的数据,返回对应位置的迭代器,支持修改。需要用到算法头文件 algorithm

    sort--排序

    用到仿函数--头文件functional

    模拟实现

    vector 相当于一个数组容器,存储的数据类型不定,因此使用模板参数来确定 vector 的类型,参考源码对迭代器进行封装,完成对 vector 的简单模拟。

    基本成员

    _start 是 vector 起始位置的指针

    _finish 是 vector 最后一个数据下一个位置的指针

    _endofstoage 是 vector 容量末尾下一个位置的指针

    1. namespace myvector
    2. {
    3. template<class T>
    4. class vector
    5. {
    6. public:
    7. typedef T* iterator;
    8. typedef const T* const_iterator;
    9. //构造函数
    10. vector()
    11. :_start(nullptr)
    12. , _finish(nullptr)
    13. , _endofstoage(nullptr)
    14. {}
    15. //析构函数
    16. ~vector()
    17. {
    18. if (_start)
    19. {
    20. delete[] _start;
    21. _start = _finish = _endofstoage = nullptr;
    22. }
    23. }
    24. private:
    25. iterator _start;
    26. iterator _finish;
    27. iterator _endofstoage;
    28. };
    29. }

    迭代区间构造

    使用的是时候,我们了解到可以传递迭代器区间进行初始化,但是不确定是什么类型的迭代器,在这里可以用模板参数来代替具体的迭代器类型。

    1. //带参构造
    2. template<class InputIterator>
    3. vector(InputIterator first, InputIterator last)
    4. :_start(nullptr)
    5. , _finish(nullptr)
    6. , _endofstoage(nullptr)
    7. {
    8. while (first != last)
    9. {
    10. push_back(*first);
    11. first++;
    12. }
    13. }

    拷贝构造和赋值重载

    用现代写法来进行拷贝构造,tmp使用上面的迭代器区间进行初始化,交换对应的指针,令tmp的指针指向未初始化的内容,最后tmp出了作用域被释放。

    注意赋值重载时的参数,是一个传值传参,会调用构造函数生成一个临时对象,令要构造的对象和其进行交换,最后释放临时对象。

    1. void swap(vector& v)
    2. {
    3. std::swap(_start, v._start);
    4. std::swap(_finish, v._finish);
    5. std::swap(_endofstoage, v._endofstoage);
    6. }
    7. //拷贝构造--现代写法
    8. vector(const vector& v)
    9. :_start(nullptr)
    10. , _finish(nullptr)
    11. , _endofstoage(nullptr)
    12. {
    13. vector tmp(v.begin(), v.end());
    14. swap(tmp);
    15. }
    16. //赋值
    17. vector& operator=(vector v)
    18. {
    19. swap(v);
    20. return *this;
    21. }

    指定初始化的个数和参数

    有这个函数,我们可以指定初始化时不同类型数据的个数;

    这里要注意的是:重载的此函数

    如果没有 int 这个类型的重载的话,当我们要初始化5个int 类型的值时,会出现:

    原因是 10个 是 int 类型 ,数据 2 也是int 类型,两个相同的类型,会调用有相同类型的参数的函数,这就导致了函数调用的错误;

    因此我们在此函数下,重载一个 int 类型的函数,因为整数是会优先匹配int类型。

    1. vector(size_t n, const T& val = T())
    2. :_start(nullptr)
    3. , _finish(nullptr)
    4. , _endofstoage(nullptr)
    5. {
    6. reserve(n);
    7. for (size_t i = 0; i < n; i++)
    8. {
    9. push_back(val);
    10. }
    11. }
    12. vector(int n, const T& val = T())
    13. :_start(nullptr)
    14. , _finish(nullptr)
    15. , _endofstoage(nullptr)
    16. {
    17. reserve(n);
    18. for (size_t i = 0; i < n; i++)
    19. {
    20. push_back(val);
    21. }
    22. }

    begin 和 end

    返回的分别是 vector 起始位置的迭代器与最后一个数据的下一个位置的迭代器

    1. iterator begin()
    2. {
    3. return _start;
    4. }
    5. iterator end()
    6. {
    7. return _finish;
    8. }
    9. const iterator begin() const
    10. {
    11. return _start;
    12. }
    13. const iterator end() const
    14. {
    15. return _finish;
    16. }

    size 和 capacity

    这两个函数分别返回 vector 中有效数据的个数和空间容量

    1. size_t size() const
    2. {
    3. return _finish - _start;
    4. }
    5. size_t capacity() const
    6. {
    7. return _endofstoage - _start;
    8. }

    reserve--开辟空间不初始化

    当要开辟的空间大于原容量大小的时候,才允许空间开辟

    解决野指针问题

    当开辟新空间时,_start、_finish、_endofstoage 都指向原来的旧空间;

    将旧空间的数据拷贝至新空间后,及时更新,避免野指针问题

    解决深浅拷贝问题

    一开始使用 memcpy 来完成原始数据到新空间的拷贝是没问题的,但如果出现 vector 内存储的是其他 vector 类型的指针,使用 memcpy 就会出现浅拷贝的问题,析构的时候同一块地址空间会析构两次,报错,这里用赋值操作进行深拷贝。

    1. void reserve(size_t n)
    2. {
    3. //解决野指针问题--提前将大小算出来保存
    4. size_t sz = size();
    5. if (n > capacity())
    6. {
    7. T* tmp = new T[n];
    8. if (_start) //判断空,不是空才能拷贝数据和释放
    9. {
    10. //memcpy(tmp, _start, size()*sizeof(T)); //开辟新空间将旧的数据拷贝进去
    11. for (size_t i = 0; i < size(); i++)
    12. {
    13. tmp[i] = _start[i];
    14. }
    15. delete[] _start;
    16. }
    17. _start = tmp;
    18. //这里_start已经更新,这两行计算size和capacity会引起野指针问题
    19. //_finish = _start + size();
    20. //_endofstoage = _start + capacity();
    21. _finish = _start + sz;
    22. _endofstoage = _start + n;
    23. }
    24. }

    insert--指定插入

    插入时首先判断空间是否足够,是否需要扩容;

    pos为插入数据的位置,记得更新pos,若不更新pos,则pos还指向原空间,访问时会出现野指针的问题。

    更新后,插入位置以后的值从后往前依次往后挪动以为,将pos位置腾出来插入新的数据,最后_finish 自增1,返回 pos 位置的值

    为何 insert 要有返回值?

    那为何第一个参数不用引用?

    一般传参时,第一个参数也可以传函数,比如 begin ,返回的时是起始位置的迭代器,函数的返回值是临时对象,具有常性,传过去是权限的扩大

    那给参数加 const 不行吗?

    不行,我们的 pos 是要更新的,不能加 const

    1. iterator insert(iterator pos,const T& x)
    2. {
    3. assert(pos >= _start && pos <= _finish); //判定范围
    4. if (_finish == _endofstoage) //要扩容--会出现迭代器失效的问题
    5. {
    6. size_t rpos = pos - _start;
    7. size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
    8. reserve(newCapacity);
    9. pos = _start + rpos;
    10. }
    11. //挪数据
    12. iterator end = _finish - 1;
    13. while (end >= pos)
    14. {
    15. *(end + 1) = *end;
    16. end--;
    17. }
    18. *pos = x;
    19. _finish++;
    20. return pos;
    21. }

    erase--指定删除

    判断要删除位置是否在有效区间内;

    将要删除位置之后的所有数据,从前往后依次向前覆盖一位,最后 _finish 自减1

    1. iterator erase(iterator pos)
    2. {
    3. assert(pos >= _start && pos < _finish);
    4. iterator it = pos + 1;
    5. while (it != _finish)
    6. {
    7. *(it - 1) = *it;
    8. it++;
    9. }
    10. _finish--;
    11. return pos;
    12. }

    尾插和尾删

    复用 insert 和 erase

    1. void push_back(const T& x)
    2. {
    3. insert(end(), x);
    4. }
    5. void pop_back()
    6. {
    7. erase(end()-1);
    8. }

    [] 重载

    1. T& operator[](size_t pos)
    2. {
    3. assert(pos < size());
    4. return _start[pos];
    5. }
    6. const T& operator[](size_t pos) const
    7. {
    8. assert(pos < size());
    9. return _start[pos];
    10. }

     

  • 相关阅读:
    html之表单元素
    ESWC 2018 | R-GCN:基于图卷积网络的关系数据建模
    轮播图的实现
    Android端ReactNative环境搭建——下
    机器学习笔记(1)常见符号,批次梯度下降,随机梯度下降
    外汇天眼:掌握这个方法,你也能成为交易高手!
    因势而变,因时而动,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang泛型(generic)的使用EP15
    【攻防世界-misc】simple_transfer
    计算机图形学之应用程序
    C++ list的模拟实现
  • 原文地址:https://blog.csdn.net/weixin_53316121/article/details/126464961