容器就是保存数据的数据结构。常用的数据结构有:数组,链表,树,哈希表,堆,栈,队列,集合等。这些数据结构可以分为序列式和关联式。

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里面拿。