1. deque 容器
1.1. buffer
- 元素是存放在一个个buffer中的
- 图中阴影部分就是未存放数据的 buffer
- 因为是一段段 buffer,所以 deque 是 分段连续
- 每个 buffer 是放在一个 vector 之中的,vector 中的每个元素是一个指针,指向每个buffer
- buffer 的次序就看 vector 中指针的次序
- deque 往后扩充,只需要在 vector 后方用一个指针指向一个新的 buffer 就可以实现扩充
- 向前扩充同理,也是让一个指针指向 buffer
- 这样就可以实现双向扩充
1.2. iterator
- deque的迭代器是一个class
- 有 cur, first, next, node
- 如果我们成 vector 为控制中心,那么可以说 node 指向的就是控制中心,当我们要进行++操作的时候,如果到了一个 buffer 的末端,就可以通过 node 移到下一个buffer 中,实现分段连续
- first 和 last 指的就是当前 node 指向的 buffer 的头部和尾部
- cur 是当前指向的 buffer 中的元素
1.3. G2.9 源码
1.3.1. 类内变量
- start 和 end 就是指向 map 的头和尾的 iterator
- map_pointer 的类型是 T**
- 因为 map 里的每一个元素都是指针,所以类型是 T**
- size_type 是unsigned int
- 所以 sizeof(deque) 是 4(T**) + 4(unsigned int) + 4 * 4 * 2(iterator 内部有四个指针) = 40
- 在这个版本中,可以指定每个 buffer 的 size
- 如果没有传入参数,默认值是 0,在定义 buffer size 的函数中,如果是 0 ,若 sizeof( value_type ) 小于 512,就传回 512 / sizeof( value_type ),如果大于,就传回 1,即每个 buffer 的 size 为 1
1.3.2. 迭代器
- 这是迭代器的源码
- 可以看到 iterator_category 是 random_access_iterator_tag 随机访问的,因为 deque 对于使用者来说是连续的容器
1.3.3. 插入元素
- 插入元素,会先判断插入的位置离头部比较近还是尾部,如果离头部近,就将要插入的位置之前的元素往前移一个位置,腾出位置,如果离尾部近则反之
- 判断的方法是通过将 index 和 size() / 2 作比较
1.3.4. 如何模拟连续空间 iterator 实现 操作符重载
- 取 front() 就是取 *start
- 取 back() 取得是 finish 的 前一个,因为 finish 和 end() 的设计一样,都是最后一个元素的下一个位置
- size() 就是 finish - start , 由于不是连续空间,所以减号也是做了重载的
- empty() 就是判断 finish 是否和 start 相等
- operator*() 取值,对 cur 解引用
- operator->() 借用了 *,取的是 cur 的地址,返回一个指向 cur 地址的指针
- operator-() buffer_size * 首尾 buffer 之间的 buffer 数量(node - x.node - 1) + 当前 buffer 中的元素个数 ( cur - first ) + 起始 buffer 元素个数 (x.last - x.cur) 根据 size 源码 return finish - start,这里的 node 就是 finish 所在的 node,cur 就是 finish 所在的 node 的最后一个元素
- ++、--操作,都是写好了 前++,前--,然后 后++、后-- 调用 前++、前-- 来实现
- operator++() 前++,先将 cur 后移,判断 cur 是否等于 last,如果 cur 是 last(最后一个元素的下一个位置),就把 node 后移到下一个缓冲区,并把 cur 设置为 first
- operator--() 前--,先判断 cur 是否为 first,如果是,那node 就返回,指向上一个 buffer,并让cur等于last,并对 cur 进行 -- 操作,只想最后一个元素
- operator+=() +=时要判断是否落在同一缓冲区,如果跨越缓冲区就要计算跨越的缓冲区数量,并切换到正确的缓冲区,在正确的位置插入元素,
- offset 是计算要偏移后的位置,node_offset 是要偏移的 node 的个数,最后还要计算 cur 要在正确的 buffer 中偏移几个元素,并偏移到正确位置
- operator-=() 直接调用+= 取相反数
- operator[]() 就是要取出第 n 个元素,直接利用重载的 +,偏移到相应位置,就可以取出这个值
1.4. G4.9 源码
- 和其他容器一样,分为了五个class,deque 继承 base,base 中包含 impl,impl 继承 allocator,且impl 包含 iterator
- G2.9 中,可以自己指定 buffer 的大小,在 G4.53 之后,这个功能被删除了
- buffer_size 的处理方法和之前一样,都是 512 / sizeof(value_type)
- G4.9 的结构
- 控制中心是个 vector,之间存放的是指向各个 buffer 的指针,当 vector 空间使用完之后,就会两倍扩充,扩充之后,将vector内原来的元素 ( 指向各个buffer的指针 ) 放在中间位置,而非两端
2. queue 容器
- queue 默认内部有一个 deque
- queue 的特点是 先进先出
- 只需要提供一些queue特有的接口就行了
- push() 就是 push_back(),而 pop() 就是 pop_front()
3. stack 容器
- stack 默认内部有一个 deque
- stack 的特点是 先进后出
- 只需要提供一些stack特有的接口就行了
- push() 就是 push_back(),而 pop() 就是 pop_back()
4. stack 和 queue
4.1. 相同
- 二者都不允许遍历,也不提供 iterator,如果要取 iterator,编译器会报错
- stack 和 queue 除了可以选择 deque 来实现,也可以利用 list,不提供模板参数默认是 deque,如果要采用 list,可以将第二个模板参数改为 list
- 利用 deque 的效率较 list 更高
- 都不可以采用 set 或者 map 作为底层结构
- set 因为没有 push_back() ,back() ,pop_back() 接口
- map 要的模板有 key 和 value,而 queue 和 stack并没有 k - v 对,一开始定义的时候就不通过
4.2. 不同
- stack 可以用 vector 来实现
- queue 则不能,因为 queue 需要 pop_front(),而 vector 没有 pop_front()
- 但是并不是说不可以采用 vector 来定义一个 queue
- 只不过当调用 pop() 的时候,编译器会报错,假设没有调用这个函数,那么就可以运行,在编译时才会判断是否可行,没有调用pop(),即可编译通过