容器就是保存数据的数据结构。常用的数据结构有:数组,链表,树,哈希表,堆,栈,队列,集合等。这些数据结构可以分为序列式和关联式。
STL基本都有这些数据结构的实现(以容器的方式)
序列式容器
- array 数组
- vector 动态数组
- list 双向链表
- forward_list 单链表
- deque 双端队列
- priority_queue 堆(优先队列)
- stack 栈
- queue 队列
- string 字符串,通常不当成容器。
关联式容器(这一块感觉书中的内容太落后的,看了下c++primer,重新整理了下。
- set 集合
- map 映射
- multiset 关键字可重复的set
- multimap
- unordered_set 关键无序的set
- unordered_map
- unordered_multiset
- unordered_multimap,看名字就知道这是啥了..
vector
vector的迭代器
vector的迭代器需要支持随机访问的效果,需要支持iterator+=n,所以是一种随机访问迭代器。本质上的实现是一个普通指针。
vector底层存储
vector底层用3个迭代器保存当前的存储状况。start指向数据开始,finish指向数据结束。但是vector是动态数组,为了不必要每次都申请内存,它会预先申请一堆内存备用。endOfStorage指向的就是可用内存的最后一个。
vector的构造与内存管理
这里说两点:
- 在实现vector的时候,怎么批量构造成员
- 当容量不够了,怎么扩容
对于第一个问题,在分配器那一节,说到有几个全局函数,可以利用stl中的那些函数,批量对某些已经申请的内容,进行构造。
对于第二个问题,首先,需要申请一块更大的区域。具体申请多少msvc和gcc是不一样的,msvc好像是2,gcc好像是1.5(或者反过来);其次,需要将原来的数据拷贝到新内存;最后,释放掉原来的内存。当然,这些都可以交给分配器去做。
vector的基本操作
这些都相当ease,本质上就是数组的存取,插入等等。唯一麻烦的就内存管理,当容量不够了需要扩容,以及后续的一系列操作。
还有个问题是迭代器失效。对于vector而言,只要是涉及到对vector数据存储布局做修改的,都会导致迭代器失效。具体来说:
- push_back,这个是因为可能导致扩容而失效
- insert,之后的迭代器失效
- erase,同上
- pop_back()不会导致失效
list
list底层是一个双向链表,它的首位是相连的,是一个环形链表。list设计一个节点类,里面保存了next和pre指针。
这里需要注意的是,end()表示一个不合法位置。所以我们需要一个额外的节点来标识,这是链表的尾部。这个思想在c/c++数据结构里用的很多。
list的迭代器
list的迭代器应该是一个指向某个节点的指针。在STL中,对迭代器进行了包装,设计了一个list_iterator类。这里的迭代器不再原生指针了。
list的内存管理
list 的内存可比vector好管理多了(不是)。直接交给分配器就行了。每次需要一个新的节点,就像分配器申请。当然,这里的分配器也是做了一层包装。可以直接选择分配多少个节点的。
实际上这种链表的存储结构,好像都是用池技术来弄的。就是先new一堆node放在nodelist或者nodepool里面,需要新的node就从pool里面拿。