顺序容器
1. 顺序容器
容器 | 特点 |
---|---|
vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 |
deque | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快 |
list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度很快 |
forward_list | 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作都很快 |
array | 固定大小数组。支持快速随机访问。不能添加或删除元素 |
string | 与vector类似的容器。专门用于保存字符。随机访问快。在尾部插入/删除速度快 |
2. 迭代器
迭代器范围
一个迭代器范围由一对迭代器表示,这两个迭代器通常被称为begin和end(first和end),它们标记了容器中元素的一个范围。
迭代器范围中的元素包含first所表示的元素以及从first开始直至last(但不包含last)之间的所有元素。
这种元素范围被称为左闭合区间:
[begin, end)
3. 元素访问
at 和 下标操作只适用于string、vector、deque、array。
c[n] 返回c中下标为n的元素的引用,n是一个无符号整数。若n>=c.size(),则函数行为未定义。
c.at[n] 返回下标为n的元素的引用。如果下标越界,则抛出一out_of_range异常。
at操作符多了下标检查。
4. 改变容器大小
resize 不适用于array
resize只改变容器中元素的数目,而不改变容器的容量。
c.resize(n) 调整c的大小为n个元素。若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化。
c.resize(n, t) 调整c的大小为n个元素。任何新添加的元素都初始化为值t
5. vector对象的增长
当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间需求更大的内存空间。
容器预留这些空间作为备用,可用来保存更多的新元素。
capacity 和 reserve 只适用于vector和string
c.capacity() 不重新分配内存空间的话,c可以保存多少元素
c.reserve(n) 分配至少能容纳n个元素的内存空间
capacity 和 size
容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
vector的实现策略似乎是在每次需要分配新的内存空间时将当前容量翻倍。
只有当迫不得已时才可以分配新的内存空间。
关联容器
1. 关联容器
按关键字有序保存元素
容器 | 特点 |
---|---|
map | 关联数组;保存关键字-值对 |
set | 关键字即值,即只保存关键字的容器 |
multimap | 关键字可重复出现的map |
multiset | 关键字可重复出现的set |
无序集合
容器 | 特点 |
---|---|
unordered_map | 用哈希函数组织的map |
unordered_set | 用哈希函数组织的set |
unordered_multimap | 哈希组织的map;关键字可重复出现 |
unordered_multiset | 哈希组织的set;关键字可重复出现 |
2. map 的下标操作
map和unordered_map容器提供了下标运算和一个对应的at函数。
set类型不支持下标,因为set中没有与关键字相关联的值。元素本身就是关键字。
map和unordered_map的下标操作
c[k] //返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其进行值初始化
c.at[k] //访问关键字为k的元素,带参数检查;若k不在c中,抛出一个out_of_range异常
at带参数检查。
map对应的数据结构是红黑树。红黑树是一种近似于平衡的二叉查找树,里面的数据是有序的。查找操作的时间复杂度为 O(logN)。
unordered_map对应哈希表,哈希表的特点就是查找效率高,时间复杂度为常数级别 O(1)。
3. map的实现机制
STL封装了许多复杂的数据结构算法和大量数据结构操作。
vector封装数组,list封装链表,map和set封装了二叉树。
关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡搜索二叉树:红黑树。
1 为何map和set的插入删除效率比用其它顺序容器高?
因为对于关联容器来说,不需要做内存拷贝和内存移动。map和set容器内所有元素都是以节点的方式来存储,其节点结构和链表差不多,指向父节点和子节点。
因此插入和删除的时候只需要稍做变换,把节点的指针指向新的节点就可以了。在其中没有内存移动。
2. 为何每次insert之后,以前保存的iterator不会失效?
iterator相当于指向节点的指针,内存没有变,指向内存的指针也就不会失效,被删除的那个元素本身已经失效了。
对于vector来说:
每一次删除和插入,指针都有可能失效,因为为了保证内部数据的连续存放,iterator指向的那块内存在删除和插入过程中可能已经被其它内存覆盖或者内存已经被释放了。
调用push_back在尾部插入时,容器内部空间可能不够,需要一块新的更大的内存,只有把以前的内存释放,申请新的更大的内存,复制已有的数据元素到新的内存,最后把需要插入的元素放到最后,那么以前的内存指针自然就不可用了。
不要使用过期的iterator。
3. 为何map和set不能像vector一样有个reserve函数来预分配数据?
因为在map和set内部存储的已经不是元素本身了,而是包含元素的节点。
也就是说map内部使用的Alloc并不是map
map<int, int, less<int>, Alloc<int>> intmap;
这时候在intmap中使用的allocator并不是Alloc,而是通过了转换的Alloc。具体转换的额方法是在内部通过Alloc::rebind重新定义了新的节点分配器。
在map和set内部的分配器已经发生了变化,就无法使用reserve方法了。
4. map和set的插入和搜索速度?
在map和set中查找是使用二分查找。
也就是说,如果有16个元素,最多需要比较4此就能找到结果,有32个元素,最多比较5次。有10000个元素,最多比较log10000,最多14次。有20000个时,最多不过15次。
查找效率还是很高的。