一、扩展

 源码中用到的思想是“基地址+偏移量”,这个思想处处可见,注意留心。deque是分段连续的动态数组,其核心结构是数组+缓冲区。关于这个思想,还有些实例:二维数组、段页式地址布局 、内存池自由链表free list等,基本原理都一样。下面我就二维数组进行举例——

STL源码剖析---deque::operator +=()

深绿色为基准, 前进19步到达黄色;后退22步到达粉色。现在的问题是:不要通过演练,直接给出结果?那这是如何计算的呢?最清晰的思路是——先计算跨越多少个一维数组,再求在一维数组中的偏移量(确保偏移量不为负)。

前进或后退需要分开讨论,前进较为简单,先讨论前进——

深绿色为基准, 前进19步到达黄色;等效于以蓝色为基准,前进23步。这样做有什么好处呢?每次都是从头开始计算偏移量。高斯除法,23 / 9 = 2;取余运算,23 % 9 = 5;所以我们得到目的地在当前一维数组偏移两个一维数组,然后在一维数组内的偏移量为5。这恰恰符合我们的认知。关于后退的讨论较为麻烦——

STL源码剖析---deque::operator +=()

如果对于后退情况的处理和前进的情况处理一样的话,会出现偏移量为负的情况。例如对于棕色方块,是以深绿色为基准,后退5步得到的;等效于以蓝色为基准,后退1步。高斯除法,-1 / 9 = 0,;取余运算,-1 % 9 = -1。所以我们得到的是当前一维数组偏移0个一维数组,在一维数组内的偏移量为-1。这已经导致了矛盾的结果,为什么?跨越的一维数组个数为0表示没有跨越任何一维数组,但是一维数组内偏移量又是-1。

此时Alex Stepanov 以及 Matt AusternDavid Musser 等巨匠就想起利用同余数的性质,跨越的一维数组个数为0,一维数组内偏移量-1等效于跨越的一维数组个数为1,一维数组内偏移量是8。如此一来,就保证了一维数组内偏移量始终不为负数。源码中就是这样实现的。

-difference_type((-offset-1) / buffer_size()) - 1 

第二次减1是为了先后退一个数组;第一次减1是由于第二次减1已经后退了一个数组的关系,所以会在原先的基础上减去1个,对于临界情况进行修正。例如粉色的方块,离蓝色的区块刚好差了两个一维数组,如果没有第一次减1,计算到底偏移了几个一维数组就会出现问题。所以第一次减1的效果就是在临界时先让一维数组的偏移量减去1。

虽然讨论的是二维数组,源码是 deque::operator +=(),但是仔细揣摩的话,你会发现,方法是一模一样的。

二、源码罗列

self& operator+=(difference_type n)
{
    //计算相对于起始位置first的偏移量。
    difference_type offset = n + (cur-first);   
    //目标位置在同一缓冲区内。
    if( offset >= 0 && offset < difference_type(buffer_size()))   
    {
        cur += n;
    }
    else    //目标位置不在同一缓冲区内。
    {
        //找到正确结点。高斯除法。
        difference_type node_offset = 
            offset > 0 ? offset / difference_type(buffer_size()) 
                        : -difference_type((-offset-1) / buffer_size()) - 1;
        //切换到正确结点。
        set_node(node + node_offset);    
        //切换到正确位置。取余运算。
        cur = first + (offset - node_offset * difference_type(buffer_size()));
    }
    //返回迭代器。
    return *this;    
}

三、参考资料

【1】段页式地址布局 

相关文章: