0. 序
学习STL源码做的笔记,主线是侯捷老师的《STL源码剖析》。
本文中部分图片来自侯捷老师的PPT。
1. list
list是一个双向链表。
list容器的iterator也是一个类,需要重载*操作符、两个++的运算。因为list所占的内存空间不一定是连续的,可能是跳跃的。
iterator中至少有5个typedef(不只是list的迭代器,其他容器也是如此)
在链表尾部,刻意加了一个空节点,与“前闭后开”的思想吻合。
iterator必须能够回答algorithm的5个提问(但是实际上目前只用了3个):
但是STL中的算法,参数不一定是class,可能就是一个一般的指针比如 int* 呢?那就不能直接问,需要间接的问,所以,在标准中加入了一个中介:迭代器萃取机(iterator traits)。算法先问萃取机,萃取机再问迭代器(指针)。
注意上图中的右下角框。value_type的目的是为了声明变量,既然是声明变量,那么加上一个const的话,所声明的这个变量就不能被更改,这没有意义,所以当iterator有const修饰的时候,返回值不能带有const修饰。
完整的5个回答,如下(用的都是偏特化):
2. vector
每次空间(capacity)是两倍增长。
vector内部只用三个指针:
- start
- finish
- end_of_storage
所以,一个vector的空间大小是3个指针的大小。
因为vector的内部空间是连续的,所以vector的iterator不用做成class,只用一个普通的指针就可以了。
3. array
array就是对c语言中的数组进行封装,以更好的方式适应STL中的算法。
实现起来比较简单:
因为就是一个简单的封装,所以array没有构造函数,也没有析构函数。
4. forward_list
与双向链表list类似,只不过双向变成了单向。
5. deque
deque容器,对外连续,但内部分段实现。
内部有一个vector(类的内部命名为map),vector中每个元素保存的是一段内存空间buffer的地址。
与list类似,他所使用的内存空间可能不连续,所以deque内部的迭代器iterator也必须封装成一个class,以实现复杂的操作。
deque的iterator有4个指针:
- cur:指向buffer中具体的那个元素
- first:当前buffer的起始地址,防止iterator运算时越界
- last:当前buffer的最后地址(最后一个元素的下一个),防止iterator运算时越界
- node:记录deque中的这个元素,在内部vector中哪个下标所指的buffer内
每个deque的大小:
有两个迭代器,每个迭代器有4个指针,所以就是8个指针。
另外,还有一个内部的vector(内部名为map)的指针和map_size的指针。
一共10个指针,所以一个deque的大小就是10个指针的大小。
deque如何模拟连续?
(这是 GNU 4.9的图,相比GNU 2.9的实现来说,它搞复杂了)
连续的模拟,都是deque迭代器的功劳。
deque.size()方法中,是finish-start两个迭代器做减法,所以一定对迭代器做了重载(两个迭代器之间有多少buffer,再乘以每个buffer的大小,在加上首尾两个buffer的部分)。
对外是连续的容器,都应该提供+=、-=以及下标访问的访问方式。
6. queue
- queue先进先出。
- queue内含一个其他容器,但是限制该容器的某些功能。
- 在c++中,定义一个queue时,第二个参数可以指定queue内部使用的容器,比如list或者deque,默认是deque。但是queue的底层不能使用vector实现,因为vector没有pop方法。但是编译会过,因为类模板在编译时是没有检查的,类模板中方法是在调用时才创建。
- queue本身的功能限制了它没有迭代器,STL也没有提供迭代器。
- 不能用set或map做底层支撑。
7. stack
- stack先进后出。
- 与queue一样,stack也是内含一个其他容器,但是限制该容器的某些功能。
- 在c++中,定义一个stack时,第二个参数可以指定queue内部使用的容器,比如list、vector或者deque,默认是deque。
- 本身的功能限制了它没有迭代器,STL也没有提供迭代器。
- 不能用set或map做底层支撑。