4.STL进阶

164 阅读3分钟

一、顺序容器

  • 底层使用数组或链表来实现
  • 注意迭代器失效问题,迭代器指向的元素改变后(如扩容操作),迭代器失效
  • 各容器比较

image.png

1. vector

  • 底层数组,是连续空间,数组满了之后会自动扩容
  • 分配2倍空间-》拷贝原本数据-》释放之前的空间
  • 不支持前面的操作,因为可能要O(n)image.png

2. deque

  • 底层通过二维数组结构实现(分段连续空间),动态开辟二维数组(2,4098/sizeof(T)),然后front、tail从中间开始,分别向两端增加,内存不够时扩容
  • 二维数组结构:通过一个中控器map管理多个固定大小的缓冲区(小数组),形成逻辑上的连续空间。中控器是一个指针数组,每个指针指向一个缓冲区
  • 扩容从维度上增加2倍,如(2,~ )->(4,~),把从原来的第二维的数组,从新一维数组的第oldsize/2开始存放
  • 其实更偏向于对vector的补充,增加了"前面"的操作
  • 支持下标访问,但是要进行"地址转换",所以比vector慢

image.png image.png

3. list

  • 底层数据结构:双向循环链表
  • 和deque就在于底层实现不同,不支持下标访问

image.png image.png

二、容器适配器

  • 是另外容器的封装,底层依赖其他容器进行实现
  • 没有实现自己的迭代器
  • 下图可以看出,模板有默认底层容器

image.png

  1. stack,底层是deque
  2. queue,底层是deque,内存利用率好(不是连续空间)、刚开始就有一段内存可以直接使用而vector需要从0开始
  3. priority\_queue(本质就是大根堆、小根堆),底层是vector,因为必须得是连续的数组,否则无法下标访问

image.png

大根堆、小根堆是通过数组下标来看成树的,而不是真正组织成树,下标i的元素,其左右节点为2i+1和2i+2

三、关联容器

image.png

注意使用下标访问时,如果容器内不存在该元素,会直接创建,如map1[1]如果没有key==1的元素,会调用值的类型构造函数,创建一个键值对,所以对于自定义类型,需要提供无参构造函数

1. 无序关联容器

  • 底层使用哈希表,不会对元素排序
  • 增删查O(1)
  • map中的元素是pair类型,其中有两个元素first、second,迭代器解引用得到的就是这pair类型
  1. unordered_set,不能有重复元素
  2. unordered_multiset,可有重复的元素
  3. unordered_map
  4. unordered_multimap

2. 有序关联容器

  • 底层使用红黑树
  • 增删查O(logn)
  • 对元素进行排序,自定义类型需要重载operator>常方法bool operator<(const T &t)const;
  1. set/multiset
  2. map/multimap

四、迭代器

  • STL类型中嵌套类型
  • 存在于顺序容器和关联容器
  • 容器方法begin()/rbegin()返回正向/反向迭代器
  1. const_iterator/iterator:前者是后者的基类,只能读
  2. 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

  1. 与push、insert的区别在于可以直接传递元素构造函数参数,底层调用构造函数来生成元素
  2. 当传递的值是元素对象时,和push、insert一样,都是调用的拷贝、移动构造函数

实现

  1. 由于接收的参数是变长的,所以需要使用函数模板可变参数
  2. 可能接受左值右值,所以需要使用万能引用来推导,再使用完美转发