- 1. vector概述
- 2. vector的数据结构
- 2.1. vector的模拟实现
- 2.2. vector的构造函数
- 2.3. vector 的扩容
- 3. vector的增删查改
- 3.1. 增
- 3.2. 删
- 3.3. 查
- 3.4. 改
- 4. 总结
1. vector概述
vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们可以安心使用vector,吃多少用多少。
vector其实就是动态的顺序表,它是在管理数组,并且它是一个类模板,可以实例化为不同类型的类,来供我们使用。其中STL标准模板库给我们提供了很多的成员函数接口来供我们使用,使我们编程的效率大大提高。本篇文章将会介绍如何使用vector,还有vector的底层原理,并且对其进行一个简单的模拟实现。
2. vector的数据结构
vector所采用的数据结构非常简单:线性的连续空间。它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间的尾端。
为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充,这边是容量(capacity)的概念。换句话说,一个vector的容量永远大于或等于其大小。一旦容量等于大小,便是满载,下次再有新增元素,整个vector就得另觅居所。如下图所示
2.1. vector的模拟实现
SGI版本的源码可以看到vector的成员变量就只有三个,start,finish, end_of_storage.
还有一些typedef声明如下。
2.2. vector的构造函数
根据官方文档给出的几个构造函数,本人这里对其进行了模拟,忽略空间配置器,代码如下。
2.3. vector 的扩容
关于vector的扩容,所谓的动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间后还有可用的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。
vector的扩容机制涉及到两个函数,如下图。
它们的模拟实现具体如下:
过程如下图所示:
3. vector的增删查改
3.1. 增
vector常要的新增元素的接口是push_back 和 insert两个。
push_back的模拟实现如下图所示:
insert的模拟实现如下图所示:
关于insert这里要注意迭代器的问题。
在不涉及到扩容的时候,插入元素是没有任何问题的。
但设计到了扩容问题,迭代器就可能会有失效的问题。
我们看到运行结果是没问题,但是我们要考虑到异地扩容的问题。
在上面讲过扩容问题的时候,会涉及到异地扩容的问题,所以在insert里是对pos迭代器做了更新处理的。假如没有做处理的话,pos会有野指针的问题。
那上面运行的结果没问题也不代表迭代器没有问题,我们拿it来读取一下看看。
可以看到运行结果的返回值不是0,代表运行出错了。
3.2. 删
删常用的接口是erase和pop_back。
erase的模拟实现如下图所示:
pop_back的模拟实现如下所示:
同样的,在vector进行erase的时候也是会存在迭代器失效的问题的。
就是在删除之后,我继续对原来迭代器进行读取,发现代码是运行错误的,也就是迭代器失效了。
但是如果说,我还是要在原来迭代器的位置进行操作,我该怎么办呢?
如果是这样的话,可以用一个迭代器来接收新的返回值或者做更新处理,然后进行操作。
如下所示,这样的运行结果就是正确的了。
vector不管新增还是删除,默认迭代器都是失效的,如果想在原来迭代器的位置进行操作,可以用返回值来进行相应的操作。
3.3. 查
对于vector常见的查接口就是[]和front和back。
实际就是我们常用的数组访问元素的[],具体模拟实现如下。
front这个接口就是访问数组的第一个元素,类似a[0]。
back就是直接返回数组的最后一个元素。
3.4. 改
这个介绍一个不常用的接口,assign。
4. 总结
通过对vector的简单模拟实现,以及查看stl源码,可以很好的了解vector的底层原理,以及了解vector的优缺点,从而更好的使用官方提供的模板库。