前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【STL】vector的模拟实现

【STL】vector的模拟实现

作者头像
诺诺的包包
发布2023-10-15 13:58:11
1940
发布2023-10-15 13:58:11
举报
文章被收录于专栏:个人笔记总结个人笔记总结

 放在专栏【C++知识总结】,会持续更新,期待支持🌹

1、vector的数据结构

这里我们与SGI版本保持一致,成员变量为三个迭代器,对一些常见接口实现模拟。如下所示:

2、接口实现

2.1、构造相关

 2.1.1、空构造

对于无参的空构造,只需要将三个指针置为空即可:

 2.1.2、构造n个val
2.1.3、迭代器区间构造

vector中的迭代器实际上就是一个指针,这里先计算出first到last区间的大小,然后提前进行扩容,再对指针解引用直接push_bask即可完成迭代器区间构造。

2.1.4、拷贝构造

对于拷贝构造这里我们提供了两种方法实现,一种是比较传统的写法,即我们先进行提前扩容(提高效率),然后将待拷贝数组的元素逐个赋值拷贝即可。不过这里需要注意的是,由于vector的存储类型可能为自定义类型,因此可能会涉及到深浅拷贝的问题。为了避免浅拷贝带来的一些问题,所以我们在对赋值运算符重载时也会采用深拷贝的方式。如下所示为传统写法:

 对于拷贝构造,还有一种“现代”写法,如下:

 我们发现,“现代写法”非常的巧妙,这里为什么要构造一个临时变量tmp呢?因为假如没有这个tmp,直接用swap与v进行交换,此时就会导致原本的v变成了*this(传引用传参,对形参的改变会影响到实参),而我们想要的是在不改变原本v的情况下,*this实现拷贝构造。这种方法有点投机取巧的感觉。

2.1.5、析构

析构函数的实现很简单,直接delete后,将迭代器置空即可:

2.2、迭代器相关

对于vector中的begin,返回其首地址即start,end返回finish即可。

 我们知道,迭代器最重要的就是要实现对容器元素的访问,因此迭代器的++与解引用*操作十分重要,但是由于vector的迭代器是一个指针,而我们知道,指针本身就支持++与解引用操作,并且我们这里vector是一个连续的空间,指针++会跳过一个T类型的大小,即会指向vector<T> 中的下一个元素,因此这里我们不需要手动实现(指针本身自带)。但是对于后面的容器诸如list、set、map等,它们的迭代器就不是一个原生指针了,需手动实现,后面遇到再说。

同时,既然实现了迭代器,也就能使用范围for对容器进行遍历访问。因为范围for的底层就是迭代器。

2.3、运算符重载

2.3.1、[]重载

我们知道vector是可以用下标来实现对元素的访问,这里我们对[]进行重载,使我们的vector也支持下标访问。不过在实现时需要注意避免下标越界。

 2.3.2、=重载

上面我们在实现拷贝构造时,传统的方法中*(_start+pos)=*(v._start+pos)用到了=,假如我们不对其进行处理的话,我们知道一个类中会自带六大默认成员函数,其中就有默认赋值运算符重载,如果不涉及到向内存申请空间资源,我们就不需要手动写,但是一旦涉及到,我们就需要使用深拷贝的方式来实现。如下:

对于赋值重载,这里我们也有两种方式来实现,一种为传统方式,一种为现代方式,首先是传统版本:先将原有空间进行释放,然后开辟一块新空间,再将v中的数据一个一个拷贝过来即可:

 现代写法则于拷贝构造相同,采用“投机取巧”的方式,如下:

2.4、空间相关

2.4.1、size与capacity

我们可以利用两指针相减得到的结果为两指针之间元素的个数,这一原理来实现size与capacity,对于size来说,代表的含义为有效元素个数,所以我们只需返回finish-start即可,而capacity代表整块空间的最大容量,因此返回end_of_storage-start即可:

2.4.2、reserve与resize

在上面很多地方都用到了reserve,这里我们先来进行实现,我们知道,vector是不支持缩容操作的,因此在进行扩容之前,要对待扩容的大小n,进行判断,如果n大于capacity,我们才执行扩容:开辟一块新空间,将原空间数据逐一拷贝,然后释放原空间,最后再更新start、finish、end_of_storage即可:

 接下来是resize,我们知道,resize是直接影响finish指针,因此我们在resize时要分情况进行讨论处理:1、resize(n)时,n>=当前的size,2、n<size。

第一种情况,当n>=size时,我们要判断其是否大于capacity,如果大于capacity,我们要多进行一步扩容操作,然后改变finish的指针,使其指向n的位置,同时给扩容后的元素一个初始值val。

第二种情况就简单多了,直接移动finish指针即可。

2.4.3、empty

empty是用来判断该容器是否为空,在vector中,当finish指针与start相等时,就说明当前容器为空。

 2.5、插入删除操作

2.5.1、push_back与pop_back

首先是尾插操作push_back,在插入一个元素之前,首先要保证的是当前容器还有足够的空间用来插入,因此我们要先判断finish是否与end_of_storage相等,相等的话要进行扩容后,再插入。

 在实现尾删时我们要考虑到,当前数组是否为空。

2.5.2、insert与erase

insert实现任意位置插入,同样,只要是插入操作,在插入之前要判断是否需要扩容,然后再进行操作。对于这种顺序连续存储的元素,假如要在pos位置插入一个元素,要先将pos及其往后的所有元素整体后移一个单位,从而空出来pos位置,实现插入:

 erase是删除任意位置的元素,同样,我们要保证不能对空数组进行删除操作,假如要实现删除pos位置的元素,我们只需要将后面的元素进行往前覆盖,然后对finish进行--即可:

 2.5.3、swap

上面由于我们实现现代版本的一些操作时,用到了swap,这里我们也需要实现一下,直接用库里面的就行:

2.6、深浅拷贝带来的问题

在上文拷贝构造、赋值重载以及reserve这里,我们在进行拷贝时并没有使用memcpy来实现,而是将数据逐一赋值拷贝,这是因为memcpy是实现逐字节拷贝,也就是我们所说的浅拷贝。对于内置类型来说,用memcoy也是可以的,但是对于自定义类型,如果使用浅拷贝,就会出现一些问题:

如下,假如我们将其修改成memcpy版本来实现,如下所示为memcpy版本的拷贝构造以及扩容:

 接下来我们通过调试来观察一波:

 我们对v1和v2进行调试观察:

我们画一个分析图,就很好理解了,如下:

 因此,我们在实现时,不论时扩容,还是拷贝构造,以及赋值重载等时,都是采用遍历整个容器,将元素一一赋值拷贝,这样再碰到这种情况,就会调用string的赋值重载,而string的赋值重载在库中也是实现的深拷贝,就会避免了我们以上memcpy这种情况,如下所所示:

end.

生活原本沉闷,但跑起来就会有风!🌹

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、vector的数据结构
  • 2、接口实现
    • 2.1、构造相关
      •  2.1.1、空构造
        •  2.1.2、构造n个val
        • 2.1.3、迭代器区间构造
        • 2.1.4、拷贝构造
        • 2.1.5、析构
      • 2.2、迭代器相关
        • 2.3、运算符重载
          • 2.3.1、[]重载
          •  2.3.2、=重载
        • 2.4、空间相关
          • 2.4.1、size与capacity
          • 2.4.2、reserve与resize
          • 2.4.3、empty
        •  2.5、插入删除操作
          • 2.5.1、push_back与pop_back
          • 2.5.2、insert与erase
          •  2.5.3、swap
        • 2.6、深浅拷贝带来的问题
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档