一、顺序容器
- 底层使用数组或链表来实现
- 注意迭代器失效问题,迭代器指向的元素改变后(如扩容操作),迭代器失效
- 各容器比较
1. vector
- 底层数组,是连续空间,数组满了之后会自动扩容
- 分配2倍空间-》拷贝原本数据-》释放之前的空间
- 不支持前面的操作,因为可能要O(n)
2. deque
- 底层通过二维数组结构实现(分段连续空间),动态开辟二维数组(2,4098/sizeof(T)),然后front、tail从中间开始,分别向两端增加,内存不够时扩容
- 二维数组结构:通过一个中控器map管理多个固定大小的缓冲区(小数组),形成逻辑上的连续空间。中控器是一个指针数组,每个指针指向一个缓冲区
- 扩容从维度上增加2倍,如(2,~ )->(4,~),把从原来的第二维的数组,从新一维数组的第oldsize/2开始存放
- 其实更偏向于对vector的补充,增加了"前面"的操作
- 支持下标访问,但是要进行"地址转换",所以比vector慢
3. list
- 底层数据结构:双向循环链表
- 和deque就在于底层实现不同,不支持下标访问
二、容器适配器
- 是另外容器的封装,底层依赖其他容器进行实现
- 没有实现自己的迭代器
- 下图可以看出,模板有默认底层容器
stack,底层是dequequeue,底层是deque,内存利用率好(不是连续空间)、刚开始就有一段内存可以直接使用而vector需要从0开始priority\_queue(本质就是大根堆、小根堆),底层是vector,因为必须得是连续的数组,否则无法下标访问
大根堆、小根堆是通过数组下标来看成树的,而不是真正组织成树,下标i的元素,其左右节点为2i+1和2i+2
三、关联容器
注意使用下标访问时,如果容器内不存在该元素,会直接创建,如map1[1]如果没有key==1的元素,会调用值的类型构造函数,创建一个键值对,所以对于自定义类型,需要提供无参构造函数
1. 无序关联容器
- 底层使用哈希表,不会对元素排序
- 增删查O(1)
- map中的元素是pair类型,其中有两个元素first、second,迭代器解引用得到的就是这pair类型
- unordered_set,不能有重复元素
- unordered_multiset,可有重复的元素
- unordered_map
- unordered_multimap
2. 有序关联容器
- 底层使用红黑树
- 增删查O(logn)
- 对元素进行排序,自定义类型需要重载operator>常方法
bool operator<(const T &t)const;
- set/multiset
- map/multimap
四、迭代器
- STL类型中嵌套类型
- 存在于顺序容器和关联容器
- 容器方法begin()/rbegin()返回正向/反向迭代器
- const_iterator/iterator:前者是后者的基类,只能读
- const_reverse_iterator/reverse_iterator:注意使用方法
vector<int> vec;
auto it = vec.begin();// iterator
vector<int>::const_iterator it1 = vec.begin();//const_iterator,基类指向派生类
auto it2 = vec.rbegin();// reverse_iterator
五、泛型算法
- template+迭代器+函数对象
- 输入都是迭代器和函数对象(如less、greater)
六、emplace
- 与push、insert的区别在于可以直接传递元素构造函数参数,底层调用构造函数来生成元素
- 当传递的值是元素对象时,和push、insert一样,都是调用的拷贝、移动构造函数
实现
- 由于接收的参数是变长的,所以需要使用函数模板可变参数
- 可能接受左值右值,所以需要使用万能引用来推导,再使用完美转发