【STL】vector的模拟实现

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


1、vector的数据结构

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

【STL】vector的模拟实现_第1张图片【STL】vector的模拟实现_第2张图片

2、接口实现

2.1、构造相关

 2.1.1、空构造

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

【STL】vector的模拟实现_第3张图片

 2.1.2、构造n个val

【STL】vector的模拟实现_第4张图片

2.1.3、迭代器区间构造

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

【STL】vector的模拟实现_第5张图片

2.1.4、拷贝构造

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

【STL】vector的模拟实现_第6张图片

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

【STL】vector的模拟实现_第7张图片

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

2.1.5、析构

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

【STL】vector的模拟实现_第8张图片

2.2、迭代器相关

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

【STL】vector的模拟实现_第9张图片

【STL】vector的模拟实现_第10张图片

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

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

2.3、运算符重载

2.3.1、[]重载

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

【STL】vector的模拟实现_第11张图片

 2.3.2、=重载

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

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

【STL】vector的模拟实现_第12张图片

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

【STL】vector的模拟实现_第13张图片

2.4、空间相关

2.4.1、size与capacity

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

【STL】vector的模拟实现_第14张图片

2.4.2、reserve与resize

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

【STL】vector的模拟实现_第15张图片

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

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

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

【STL】vector的模拟实现_第16张图片

2.4.3、empty

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

【STL】vector的模拟实现_第17张图片

 2.5、插入删除操作

2.5.1、push_back与pop_back

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

【STL】vector的模拟实现_第18张图片

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

【STL】vector的模拟实现_第19张图片

2.5.2、insert与erase

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

【STL】vector的模拟实现_第20张图片 【STL】vector的模拟实现_第21张图片

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

【STL】vector的模拟实现_第22张图片

【STL】vector的模拟实现_第23张图片

 2.5.3、swap

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

【STL】vector的模拟实现_第24张图片

2.6、深浅拷贝带来的问题

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

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

【STL】vector的模拟实现_第25张图片

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

【STL】vector的模拟实现_第26张图片

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

【STL】vector的模拟实现_第27张图片

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

【STL】vector的模拟实现_第28张图片

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

【STL】vector的模拟实现_第30张图片


end.

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

你可能感兴趣的:(C++知识总结,c++,开发语言,vector,STL)