• C++第二十二弹---vector深度剖析及模拟实现(下)


     ✨个人主页: 熬夜学编程的小林

    💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

    目录

    1、容量操作

    2、内容修改操作

    3、打印函数

    4、迭代器失效

    4.1、什么是迭代器失效

    4.2、哪些操作会引起迭代器失效

    总结


    1、容量操作

    size()、capacity()

    获取容器的有效数据个数(连续内存空间的指针相减计算的就是间隔的元素个数)分配给当前空间的大小以元素个数表示。

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

     reserve(size_t n)

    扩容。如果n大于当前容量则扩容,小于等于当前容量则不处理。

    1. void reserve(size_t n)//将容量个数扩大到n
    2. {
    3. if (n > capacity())//大于容量才扩容
    4. {
    5. size_t old_size = size();
    6. T* tmp = new T[n];
    7. memcpy(tmp, _start, sizeof(T) * size());
    8. delete[] _start;//加[]
    9. _start = tmp;
    10. //_finish = _start + size();//_start的地址改变了 size()结果变化
    11. _finish = _start + old_size;
    12. _endofstorage = _start + n;
    13. }
    14. }

    这里我们开空间完成的是一个深拷贝的过程用 memcpy 将旧数组中的数据拷贝到新数组,但是memcpy 在这里基于字节的拷贝,即浅拷贝,那么,如果我们vector实例化为string类,这里string类进行浅拷贝会涉及到二次释放等问题。

    解决办法:

    通过一个循环,使用赋值操作符(自定义类型会调用赋值操作符重载)逐个拷贝旧数组中的元素到新数组。

    1. void reserve(size_t n)//将容量个数扩大到n
    2. {
    3. if (n > capacity())//大于容量才扩容
    4. {
    5. size_t old_size = size();
    6. T* tmp = new T[n];
    7. for (size_t i = 0; i < old_size; i++)
    8. {
    9. tmp[i] = _start[i];//调用赋值操作符重载,深拷贝
    10. }
    11. delete[] _start;//加[]
    12. _start = tmp;
    13. //_finish = _start + size();//_start的地址改变了 size()结果变化
    14. _finish = _start + old_size;
    15. _endofstorage = _start + n;
    16. }
    17. }

     注意:

    需要提前计算原空间的大小,防止后面计算的大小是错误的,因为扩容的时候_start指针会修改指向,而_finish还指向原空间。

    resize(size_t n)

    调整容器的大小,使其包含n个元素。

    如果n小于当前容器大小,则内容将减少到其前n个元素,删除超出的元素(并销毁它们)

    如果n大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到n的大小。如果指定了val,则将新元素初始化为val,否则初始化为缺省值。

    如果n也大于当前容器容量,则自动重新分配所分配的存储空间。

    1. void resize(size_t n,const T& val=T())//将容量修改为n个,并初始化为val
    2. {
    3. if (n > capacity())
    4. {
    5. //扩容
    6. reserve(n);
    7. while (_finish < _start + n)
    8. {
    9. *_finish = val;
    10. ++_finish;
    11. }
    12. }
    13. else
    14. {
    15. //删除
    16. _finish = _start + n;//更改_finish位置即可,一般不缩容
    17. }
    18. }

    注意:

    当 n 小于当前容量时,只需修改 _finish 指向即可,一般情况不缩容,如需缩容,可以调用shrink_to_fit()缩容函数。

    2、内容修改操作

    push_back()

    尾插数据。即在_finish位置插入数据,在插入数据之前需要判断空间是否已满。

    1. void push_back(const T& val)
    2. {
    3. if (_finish == _endofstorage)//扩容
    4. {
    5. reserve(capacity() == 0 ? 4 : 2 * capacity());
    6. }
    7. *_finish = val;
    8. ++_finish;
    9. }

     pop_back()

    尾删数据(有数据才能删)。删除最后一个数据,修改_finish指向即可。

    1. void pop_back()
    2. {
    3. assert(!empty());
    4. --_finish;
    5. }

    empty()

    判断容器是否为空(判断_start与_finish指向是否一致),为空返回true,否则返回false。 

    1. bool empty()
    2. {
    3. return _start == _finish;
    4. }

    insert() 

    在pos位置插入数据。

    1.使用断言保证在[_start,_finish]区间插入数据

    2.判断是否需要扩容,扩容则可能出现迭代器失效情况,则需要提前计算pos 位置与 _start之间的距离。

    3.将[pos,_finish)之间的数据都向后挪动一步,再pos位置插入数据。

    4.最后返回新的pos位置。

    1. iterator insert(iterator pos, const T& val)//在pos位置插入val
    2. {
    3. assert(pos >= _start);
    4. assert(pos <= _finish);
    5. //扩容
    6. if (_finish == _endofstorage)
    7. {
    8. size_t len = pos - _start;//标记pos与原数组起点的长度
    9. reserve(capacity() == 0 ? 4 : 2 * capacity());
    10. pos = _start + len;//扩容_start的指向修改,pos也需修改
    11. }
    12. //移动数据
    13. iterator it = _finish - 1;
    14. while (it >= pos)
    15. {
    16. *(it + 1) = *it;
    17. --it;
    18. }
    19. //填充数据
    20. *pos = val;
    21. ++_finish;
    22. return pos;//返回新的pos位置
    23. }

     erase()

    删除pos位置的数据。

    1.使用断言保证在[_start,_finish)区间删除数据,此处跟插入不同,不能删除_finsih位置数据

    2.将[pos + 1,_finish)之间的数据都向前挪动一步。

    1. iterator erase(iterator pos)//删除pos位置数
    2. {
    3. assert(pos >= _start);
    4. assert(pos < _finish);
    5. //iterator it = pos;
    6. iterator it = pos + 1;
    7. while (it < _finish)
    8. {
    9. //*it = *(it + 1);//it = pos; 越界
    10. *(it - 1) = *it;
    11. it++;
    12. }
    13. --_finish;
    14. return pos;
    15. }

    erase 返回值是一个迭代器,指向原来pos位置的下一个位置,即删除操作之后的pos位置。

    push_back()  pop_back()

    尾插和尾删函数,使用insert()和erase()函数调用。

    1. void push_back(const T& val)
    2. {
    3. insert(end(), val);//end()位置插入数据
    4. }
    5. void pop_back()
    6. {
    7. erase(end() - 1);//删除end()前面位置数据
    8. }

    3、打印函数

    print_vector()

    打印vector容器的数据(任意类型)。

    1. template<class T>//函数模板
    2. void print_vector(const vector<T>& v)
    3. {
    4. //前面加typename则没有问题,表示iterator是一个类型
    5. //typename vector<T>::iterator it = v.begin();
    6. auto it = v.begin();//此处使用auto则可以避免此问题
    7. while (it != v.end())
    8. {
    9. cout << *it << " ";
    10. it++;//指向下一个位置
    11. }
    12. cout << endl;
    13. }

     注意:

    显示访问迭代器时,需要在前面加关键字typename保证iterator是一个类型,或者直接使用auto。

    4、迭代器失效

    4.1、什么是迭代器失效

    迭代器的作用主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装。

    迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。我们可以从以下三步进行分析:

    • [1]迭代器的本质就是指针迭代器失效就是指针失效
    • [2]指针失效指针指向的空间是非法的
    • [3]指针指向非法空间:指向了被释放的空间 或者 越界访问 。

    4.2、哪些操作会引起迭代器失效

    1. 所有可能会引起扩容的操作都可能会导致迭代器失效。如:resize、reserve、insert、assign、push_back等  --------------  野指针引起的迭代器失效
    2. 指定位置的插入和删除都会都可能会导致迭代器失效。如: insert 、erase -----------------   迭代器指向的位置意义发生改变

    注意:

    上述可能会引起迭代器失效的问题,代码中基本已经解决,如果uu们发现解决的有问题可以私信博主喔!!!

    总结


    本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

  • 相关阅读:
    六、C语言循环语句
    面向对象编程原则
    网络安全--防火墙旁挂部署方式和高可靠性技术
    俄罗斯方块游戏的设计与实现(Java+Swing+Eclipse)
    阿里双十一交易核心链路产品--RocketMQ 底层原理及性能调优实战
    『力扣每日一题11』:转换成小写字母
    累计注意力大模型
    硬件工程师有没有35岁危机?
    从零开始学习Dubbo4——让模块独立运行
    java.lang.ClassNotFoundException:如何解决
  • 原文地址:https://blog.csdn.net/2201_75584283/article/details/139157829