虽然 deque 从表面上来说,它采用连续的空间,并且支持随机访问,即其迭代器为 random_access_iterators 类型。而且相比 vector,它还支持 push_front()。
//
//
但是实际上,deque 只是逻辑上的连续空间。它需要维护一个特殊的东西,叫做 deque 的中控器。该中控器主要由一个二级指针和一个 size 构成,(即可以理解为,该中控器是一个一级指针数组,下面我们用这个词来讲解)。
//
//
中控器是一个指针数组,数组中的每一个指针指向一个缓冲区 (即一块连续空间)。
初始时,会首先配置出这个中控器的空间,然后配置出需要的若干个缓冲区的空间,将这若干个缓冲区放在中控器的中间部分(使得头尾两端可扩充的缓冲区数量一样大)。
注意:只有配置了的才有空间,而且配置仅仅代表分配空间,该空间时原始的,并没有初始化。即,中控器初始时,里面的每个值都还没有构造,缓冲区也是同理。我们会在需要的时候进行构造。
//
//
当然,这就还会涉及到中控器空间不足的问题。(后述)
//
//
deque 的迭代器中包含 4 个重要部分,该迭代器指向的缓冲区 (即该迭代器指向中控器的哪一个位置),该缓冲区的首指针,尾指针 以及 当前的指针位置 cur。即,对于首迭代器来说,我们每一次构造都是先 – cur,然后在 cur 这个位置上进行构造;对于尾迭代器,我们在 cur 位置上进行构造,然后 ++ cur。(这里不包括缓冲区用尽的情况)
你可以将迭代器理解为一个指针,指向中控器的某一个位置,但这个指针同时还包含一些额外的信息。
//
//
那么中控器是如何维护的呢。其实就是两个迭代器,首和尾迭代器。可以把首尾迭代器想象成两个指针,指向中控器中目前使用的首位置和尾位置。所以当缓冲区用尽时,假设是首迭代器的缓冲区用尽,我们就会配置一个新的缓冲区,然后将缓冲区的首指针放在中控器中的首迭代器的前一个位置,然后更新首迭代器;如尾迭代器的缓冲区用尽,我们就会配置一个新的缓冲区,然后将缓冲区的首指针放在中控器中的尾迭代器的后一个位置,然后更新尾迭代器。(当然,这里未考虑中控器空间不足的情况)。
//
//
而逻辑上,deque 的迭代器是 random_access_iterators,所以我们还需要为它重载 ++,+=, --, -=, -, +, <, >,[] 等运算符。即满足 random_access_iterators 的所有操作。
//
//
当中控器空间不足时,会考虑到能否通过在中控器中移动 [start, finish+ 1) 使得 start 和 finish 两端有多余空间(足以满足需求的新增数量),而决定是否真正的配置新的中控器空间。
若配置新的空间,就需要将原来的内容拷贝到新空间中,然后释放旧空间。
//
//
实现还是有些复杂,但原理是这样。