最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的《STL源码剖析》。之所以看这本书主要是由于我过去曾经接触过一些*人,我一直觉得*人非常不错(这里不涉及任何政治,仅限个人感受),在技术上他们比较严谨,在为人处世上也非常谦虚,所以一些*的技术资料我觉得是值得一看的。
想要学习STL源码的设计,其实应该是从空间适配器(allocator)和迭代器(iterators)开始看起的,但是我没有对这两个部分做深入研究,主要原因是最近实在太忙,要写论文又要兼顾找工作,不能在这上面投入太大精力,而这两个部分又不太容易看懂,所以我决定先从容器开始看起,这样在编码时用到c++标准库的时候会更有底气。
关于容器的设计,其实我们在算法与数据结构课程里都涉及过,但是当时主要集中于理解设计理念,没有真正编过一套完整的库去应用我们学习到的知识,所以读者在学习c++标准库的时候我觉得应该结合过去数据结构的知识,关注它实现的技巧和为什么这么实现,通过学习优秀代码提升自己的编程能力。
容器分为序列式容器与关联式容器,本篇博客主要讨论序列式容器,所谓序列式容器,其中的元素都可序(ordered),但是未必有序(sorted)。C++本身提供了一个序列式容器array,STL另外再提供vector、list、deque,再以此为基础实现heap(内含vector),stack和queue(内含deque),后面会展开叙述,其实这里的stack和queue只是将deque改头换面而形成的,技术上被归类为一种配接器(adapter)。
由于篇幅,本文还是从使用者的角度介绍各个接口的设计思路。
Vector
Vector的迭代器
vector维护的是一个连续线性的空间,所以它的迭代器很好实现,只需要一个普通指针就可以(和元素类型无关),迭代器所需要的操作行为包括:operator*,operator->,operator++,operator--,operator+,operator-,operator+=,operator-=,普通指针天生就具备。,vector支持随机存取,它提供的是Random Access Iterators,普通指针也有这样的能力。所以,如果客户端写出这样的代码:
vector<int>::iterator ivite;
那么ivite的类型就是int *。
vector的构造与内存管理
vector里面有三个迭代器,start、finish、end_of_storage,分别指向当前连续空间所使用的头和尾、整块连续空间的尾。为了降低空间配置时的速度成本,vector实际配置的大小要比客户端需求量更大一些,以备将来的使用。vector空间的增长实际上是一个浩大的工程,开销非常大,它需要申请一块新的连续空间,然后把已有的内容拷贝过去,这里面每次新扩充的空间都是原空间的两倍大,这样做可以把扩充空间的时间复杂度降低到O(n),如果每次新扩充的空间相比于原空间增加一个固定的大小m,那么无论m取何值,都可以证明最后的时间复杂度正比于n的平方。
vector元素操作:push_back,pop_back,erase,clear,insert
void push_back(const T& x) //将元素插入于vector的尾端,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,调整迭代器finish,如果没有备用空间,就
//扩充空间(重新配置、移动数据、释放原空间)
void pop_back() //将尾端元素删除。
iterator erase(iterator first,iterator last) //删除[first,last)中的所有元素
iterator erase(iterator position) //清除某个位置上的元素
void clear() //清除所有元素
void insert(iterator postion,size_type n,const T& x) //从position开始,插入n个元素,元素初值为x
list
list和vector是两个最常用的容器,list的特点是:每次插入或删除一个元素,就配置或释放一个元素空间。list对于空间的运用有绝对的精准,而且对于任意位置的元素插入或删除的时间复杂度是O(1),但是list不支持随机访问,它只能从头开始遍历元素,这点不如vector,因此它们的使用需要根据具体的情况具体分析。
list的迭代器
list不像vector一样可以用普通指针作为迭代器,因为其结点不保证在存储空间中连续存在。list迭代器需要能够指向list的节点,并且可以进行正确的递增、递减、取值操作、成员取用操作,其实都很简单,这里不列代码了。