vector的元素排列和操作方式与array很相似,不同的是vector是动态空间,能够随着元素的添加自动扩展空间。它的实现关键在于对大小的控制以及重新配置空间时移动元素的效率。
vector的iterator定义是value_type*,也就是普通的指针。因为vector维护的空间是一个连续线性空间,所以普通的指针就能满足vector迭代器的所有需求
vectord的构造与内存管理
vector的定义默认使用的是alloc作为空间配置器,并且为了方便以元素大小为配置单位定义了data_allocator。data_allocator::allocate(n)会分配n个元素空间大小,uninitialized_fill_n则会根据迭代器的型别特性(详见《STL源码剖析》笔记-迭代器iterators)选择效率较高的方式进行构造。
当使用push_back()将元素插入尾部时,会先检查是否有备用空间,如果有就直接在备用空间中构造,并调整迭代器finish。否则,就需要进行扩展空间,vector扩展空间的方式是申请新的空间,然后把原有的内容复制过来,然后释放原有空间,这是为了保证空间的连续性。因此,一旦发生了空间的扩展,原有的迭代器全都会失效,这点需要特别注意。
相较于vector的连续线性空间,list就复杂许多,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素移除,list永远是常数时间。
list的节点
template
struct __list_node
{
typedef void* void_pointer;
void_pointer next; // 前后指针都是void*类型
void_pointer prev;
T data;
};
以上是list节点的结构体,可以看出list是一个双向链表。
list的迭代器
ist和vector不同,不能使用原始指针作为迭代器的value_type,因为list不能保证元素是线性连续的。list的迭代器需要具备前移、后移的能力,因此是Bidirectional Iterators。list的插入操作不会造成迭代器失效,删除操作只导致被删除的迭代器失效。
deque是一种双向开口的分段连续线性空间,可以在头部/尾部进行元素的插入和删除。它与vector最大的差异,一是deque允许于常数时间内对头端进行插入或删除元素,二是deque是由分段连续线性空间组合而成,随时可以增加一段新的空间,不像vector那样,vector当内存不够时,需要重新分配空间。
deque也提供Ramdom Access Iterator,但是不是原始指针,相比起来要复杂得多,因此也影响了运算效率,所以尽量使用vector少用deque。
deque的中控器
deque在逻辑上是连续线性空间,由一段一段的定量连续空间构成。一旦需要在前端或尾端增加新空间,就配置一段定量的连续空间进行串接。与vector相比避免了重新配置空间的消耗,代价就是更复杂的迭代器。为了管理这些分段空间,deque引入了所谓的map作为主控。map是一小块连续空间,其中每个元素都是指向缓冲区的指针,缓冲区才是deque存储数据的主体:
deque的迭代器
deque分段连续空间的实现,主要有迭代器的operator++和operator–来负责。deque迭代器必须能够判断目前的缓冲区,以及是否在当前缓冲区的边界,这样才能决定是否要跳转到另一个缓冲区。而为了找到其他缓冲区,必须要能够控制中控器map。
deque的迭代器对各种指针运算都进行了重载,其中最关键的就是当遇到缓冲区边缘时需要特殊处理:
deque处理维护指向map的指针外,还需要维护start和finish两个迭代器,分别是第一个缓冲区的第一个元素和最后一个缓冲区的最后一个元素后一个位置。还需要记录当前map的大小,如果空间不足就需要重新配置:
deque在开始和最后添加元素都一样快,并提供了随机访问方法,像vector一样使用[]访问任意元素,但是随机访问速度比不上vector快,因为它要内部处理堆跳转
默认底层容器是deque