STL中一些常见的容器:
-
序列式容器(Sequence Containers):
- vector(动态数组): 动态数组,支持随机访问和在尾部快速插入/删除。
- list(链表): 双向链表,支持在任意位置快速插入/删除。
- deque(双端队列): 双端队列,支持在两端快速插入/删除。
容器适配器(Container Adapters):
- stack(栈): 后进先出(LIFO)的数据结构。
- queue(队列): 先进先出(FIFO)的数据结构。
- priority_queue(优先队列): 具有优先级的队列。
-
关联式容器(Associative Containers):
- set(集合): 有序集合,不允许重复元素。
- multiset(多重集合): 有序集合,允许重复元素。
- map(映射): 键-值对的集合,按键有序存储,不允许重复键。
- multimap(多重映射): 键-值对的集合,按键有序存储,允许重复键。
无序关联容器(Unordered Associative Containers):
- unordered_set: 无序集合,不允许重复元素。
- unordered_multiset: 无序集合,允许重复元素。
- unordered_map: 无序映射,按键无序存储,不允许重复键。
- unordered_multimap: 无序映射,按键无序存储,允许重复键。
序列式容器
1.vector
1.1定义
C++本身提供了一个序列容器array数组,STL中新增了一个vector,被称为动态数组。
它与array的区别是它提供了动态空间大小。
例如:
std::array<int, 5> myArray = {1, 2, 3, 4, 5};
std::vector<int> myVector = {1, 2, 3, 4, 5};
1.2迭代器
vector的迭代器所需功能,普通指针就能满足,所以在vector容器中,迭代器就是普通指针,并且其类型属于随机访问迭代器,支持跳跃式的访问。支持++,—,+=,-=等操作运算。
所以vector容器元素的访问的时间复杂度是O(1).
1.2数据结构
vector的数据结构是一块连续的线性空间。其中以两个迭代器start和finish分别指向配置得来的连续空间中已被使用的范围,以迭代器end_of_storage指向整块连续空间的尾端:
template <class T,class Alloc=alloc>
class vector
{
...
protected:
iterator start;//目前使用的空间的头部
iterator finish;//目前使用的空间的尾部
iterator end_of_storage; //目前可用空间的尾部
...
}
为了减少空间动态配置时的成本,vector实际配置的大小(capacity)可能比需求更大一些,以备将来可能的扩充。vector这里就有一个容量capacity的概念。和vector大小size的概念对应。vector的容量永远大于或等于其大小。一旦容量等于大小,便是满载,下次再有新增元素,整个vector就要进行转移。
1.3vector的push_back(如何动态增长)
其中核心点在于insert_aux函数
template <class T,class Alloc>
void vector<T,Alloc>::insert_aux(iterator position,const T& x)
{
//检查是否还有备用空间
if(finish!=end_of_storage){
//在备用空间起始处构造一个元素,并以vector最后一个元素为其初始值
construct(finish,*(finish-1));
//移动finish指针
++finish;
//拷贝内容
T x_copy=x;
//执行拷贝
copy_backward(position,finish-2,finish-1);
*position=x_copy;
}else {
//进入这里表示已经不存在可用空间
const size_type old_size=size();
//决定分配空间的大小
const size_type len=old_size!=0?2*old_size:1;
//实际分配
iterator new_start=data_allocator::allocate(len);
iterator new_finish=new_start;
try{
//拷贝元素
new_finish=uninitialized_copy(start,position,new_start);
//为新元素设定初值x
construct(new_finish,x);
//调整指针位置
++new_finish;
}catch(...){
destroy(new_start,new_finish);
data_allocator::deallocate(new_start,len);
throw;
}
//释放原来的内存
destroy(begin(),end());
deallocate();
//调整迭代器,指向新vector
start=new_start;
finish=new_finish;
end_of_storage=new_start+len;
}
}
可以看出来vector所谓的动态增加大小,并不是在原有空间之后接新的空间(因为无法保证原空间之后还有连续的可配置空间),而是以原大小的两倍来另外配置一块较大的空间,然后再将原有内容拷贝过来,并释放原空间。因此需要注意的是vector一旦引起了的空间重新配置,指向原vector的所有迭代器都失效了。
1.4vector的pop_back、erase、insert
pop_back和erase比较简单,如下所示:
- insert存在三种情况。
例如插入代码insert(iter, 2, 1),表示从位置iter开始插入,插入2个为1的数。
- 插入n个数后,没有引发动态配置空间。且插入的数量大于插入点之后的现有元素个数;
- 插入n个数后,没有引发动态配置空间。且插入的数量小于等于插入点之后的现有元素个数;
- 插入n个数后,引发了动态配置空间。
3种情况的源码和示意图如下:
由于是在原有的空间上来构造插入数据,没有生成多余的临时变量(尽可能地压榨性能),所以会有以上两种情况。
三种情况插入都会引发迭代器失效问题,前两种会导致position之后的所有迭代器均失效,最后一种情况会导致所有迭代器失效。