放在专栏【C++知识总结】,会持续更新,期待支持
这里我们与SGI版本保持一致,成员变量为三个迭代器,对一些常见接口实现模拟。如下所示:
对于无参的空构造,只需要将三个指针置为空即可:
vector中的迭代器实际上就是一个指针,这里先计算出first到last区间的大小,然后提前进行扩容,再对指针解引用直接push_bask即可完成迭代器区间构造。
对于拷贝构造这里我们提供了两种方法实现,一种是比较传统的写法,即我们先进行提前扩容(提高效率),然后将待拷贝数组的元素逐个赋值拷贝即可。不过这里需要注意的是,由于vector的存储类型可能为自定义类型,因此可能会涉及到深浅拷贝的问题。为了避免浅拷贝带来的一些问题,所以我们在对赋值运算符重载时也会采用深拷贝的方式。如下所示为传统写法:
对于拷贝构造,还有一种“现代”写法,如下:
我们发现,“现代写法”非常的巧妙,这里为什么要构造一个临时变量tmp呢?因为假如没有这个tmp,直接用swap与v进行交换,此时就会导致原本的v变成了*this(传引用传参,对形参的改变会影响到实参),而我们想要的是在不改变原本v的情况下,*this实现拷贝构造。这种方法有点投机取巧的感觉。
析构函数的实现很简单,直接delete后,将迭代器置空即可:
对于vector中的begin,返回其首地址即start,end返回finish即可。
我们知道,迭代器最重要的就是要实现对容器元素的访问,因此迭代器的++与解引用*操作十分重要,但是由于vector的迭代器是一个指针,而我们知道,指针本身就支持++与解引用操作,并且我们这里vector是一个连续的空间,指针++会跳过一个T类型的大小,即会指向vector
中的下一个元素,因此这里我们不需要手动实现(指针本身自带)。但是对于后面的容器诸如list、set、map等,它们的迭代器就不是一个原生指针了,需手动实现,后面遇到再说。
同时,既然实现了迭代器,也就能使用范围for对容器进行遍历访问。因为范围for的底层就是迭代器。
我们知道vector是可以用下标来实现对元素的访问,这里我们对[]进行重载,使我们的vector也支持下标访问。不过在实现时需要注意避免下标越界。
上面我们在实现拷贝构造时,传统的方法中*(_start+pos)=*(v._start+pos)用到了=,假如我们不对其进行处理的话,我们知道一个类中会自带六大默认成员函数,其中就有默认赋值运算符重载,如果不涉及到向内存申请空间资源,我们就不需要手动写,但是一旦涉及到,我们就需要使用深拷贝的方式来实现。如下:
对于赋值重载,这里我们也有两种方式来实现,一种为传统方式,一种为现代方式,首先是传统版本:先将原有空间进行释放,然后开辟一块新空间,再将v中的数据一个一个拷贝过来即可:
现代写法则于拷贝构造相同,采用“投机取巧”的方式,如下:
我们可以利用两指针相减得到的结果为两指针之间元素的个数,这一原理来实现size与capacity,对于size来说,代表的含义为有效元素个数,所以我们只需返回finish-start即可,而capacity代表整块空间的最大容量,因此返回end_of_storage-start即可:
在上面很多地方都用到了reserve,这里我们先来进行实现,我们知道,vector是不支持缩容操作的,因此在进行扩容之前,要对待扩容的大小n,进行判断,如果n大于capacity,我们才执行扩容:开辟一块新空间,将原空间数据逐一拷贝,然后释放原空间,最后再更新start、finish、end_of_storage即可:
接下来是resize,我们知道,resize是直接影响finish指针,因此我们在resize时要分情况进行讨论处理:1、resize(n)时,n>=当前的size,2、n 第一种情况,当n>=size时,我们要判断其是否大于capacity,如果大于capacity,我们要多进行一步扩容操作,然后改变finish的指针,使其指向n的位置,同时给扩容后的元素一个初始值val。 第二种情况就简单多了,直接移动finish指针即可。 empty是用来判断该容器是否为空,在vector中,当finish指针与start相等时,就说明当前容器为空。 首先是尾插操作push_back,在插入一个元素之前,首先要保证的是当前容器还有足够的空间用来插入,因此我们要先判断finish是否与end_of_storage相等,相等的话要进行扩容后,再插入。 在实现尾删时我们要考虑到,当前数组是否为空。 insert实现任意位置插入,同样,只要是插入操作,在插入之前要判断是否需要扩容,然后再进行操作。对于这种顺序连续存储的元素,假如要在pos位置插入一个元素,要先将pos及其往后的所有元素整体后移一个单位,从而空出来pos位置,实现插入: erase是删除任意位置的元素,同样,我们要保证不能对空数组进行删除操作,假如要实现删除pos位置的元素,我们只需要将后面的元素进行往前覆盖,然后对finish进行--即可: 上面由于我们实现现代版本的一些操作时,用到了swap,这里我们也需要实现一下,直接用库里面的就行: 在上文拷贝构造、赋值重载以及reserve这里,我们在进行拷贝时并没有使用memcpy来实现,而是将数据逐一赋值拷贝,这是因为memcpy是实现逐字节拷贝,也就是我们所说的浅拷贝。对于内置类型来说,用memcoy也是可以的,但是对于自定义类型,如果使用浅拷贝,就会出现一些问题: 如下,假如我们将其修改成memcpy版本来实现,如下所示为memcpy版本的拷贝构造以及扩容: 接下来我们通过调试来观察一波: 我们对v1和v2进行调试观察: 我们画一个分析图,就很好理解了,如下: 因此,我们在实现时,不论时扩容,还是拷贝构造,以及赋值重载等时,都是采用遍历整个容器,将元素一一赋值拷贝,这样再碰到这种情况,就会调用string的赋值重载,而string的赋值重载在库中也是实现的深拷贝,就会避免了我们以上memcpy这种情况,如下所所示: end. 生活原本沉闷,但跑起来就会有风!2.4.3、empty
2.5、插入删除操作
2.5.1、push_back与pop_back
2.5.2、insert与erase
2.5.3、swap
2.6、深浅拷贝带来的问题