STL源码剖析(三):容器(2)List和sList

91 阅读2分钟

list是一个双向链表,为了满足半开半闭区间性质,还添加了一个用来标志的尾节点。

list迭代器

list插入和接合操作不会造成原迭代器失效,而在vector中是行不通的;而不能向vector用原生指针做为迭代器,因为不能直接用++操作。看下list迭代器的结构设计,成员函数都挺简单的(++ ,--,==,->啥的),就不贴上来了

struct _List_iterator_base {
  typedef size_t                     size_type;  // list迭代器类型所占字节数
  typedef ptrdiff_t                  difference_type; // 1.区间
  typedef bidirectional_iterator_tag iterator_category; // 2.迭代器类型

  _List_node_base* _M_node;  // 普通指针,指向list的节点
};  

template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator : public _List_iterator_base {
  // 用来初始化迭代器,主要用在construct里
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
  typedef _List_iterator<_Tp,_Ref,_Ptr>             _Self;

  typedef _Tp value_type;  // 3....
  typedef _Ptr pointer;   // 4....
  typedef _Ref reference;  // 5.....
  typedef _List_node<_Tp> _Node;
};

list类

list缺省使用alloc做为空间配置器,并据此定义了一个list_node_allocator,更方便地以节点大小为配置单位。

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class list : protected _List_base<_Tp, _Alloc> {
  // requirements:

  __STL_CLASS_REQUIRES(_Tp, _Assignable);

  typedef _List_base<_Tp, _Alloc> _Base;
protected:
  typedef void* _Void_pointer;    

public:      
  typedef _Tp value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef _List_node<_Tp> _Node;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;  // 为什么容器也要有 区间类型 呢?

  typedef simple_alloc<_List_node<_Tp>, _Alloc> _Alloc_type; // 定义于_List_base
  typedef typename _Base::allocator_type allocator_type;
  allocator_type get_allocator() const { return _Base::get_allocator(); }

public:
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
};

list的Sort():STL算法sort(),只接收RAI,因此list需定义自己的sort()方法,sort函数用到了splice,merge,它们又都是基于transefer函数的。

template <class _Tp, class _Alloc>
void list<_Tp, _Alloc>::sort()
{
  // Do nothing if the list has length 0 or 1.
  if (_M_node->_M_next != _M_node && _M_node->_M_next->_M_next != _M_node) {
    list<_Tp, _Alloc> __carry;
    list<_Tp, _Alloc> __counter[64];
    int __fill = 0;
    while (!empty()) {
      // 把当前链表的第一个元素放到carry的头部
      __carry.splice(__carry.begin(), *this, begin());
      int __i = 0;
      // 这个wihle循环是精华,crray不断和counter[0]、counter[1]...去合并
      // 假设和counter[0]成功合并,由于i<file,结果是counter[0]为空,结果保存到carry列表
      // 这时和counter[1]成功合并,由于i==file,结果是carry为空,结果保存到counter[1]
      // 至于设置i<fill 这个条件,是考虑合并的效率吗? 
      // 但从这个循环可以看出:第i个counter链表最多容纳2^(fill-1)个元素
      while(__i < __fill && !__counter[__i].empty()) {
        // 将两个递增列表合并,合并后仍然递增
        __counter[__i].merge(__carry);
        // 将carry链表交换给counter[i],carry为空
        __carry.swap(__counter[__i++]);
      }
      __carry.swap(__counter[__i]);         
      if (__i == __fill) ++__fill;
    } 
    // 将所有counter列表合并
    for (int __i = 1; __i < __fill; ++__i)
      __counter[__i].merge(__counter[__i-1]);
    swap(__counter[__fill-1]);
  }
}

tip:transefer(position,first,last)

slist概述

slist是一个单向链表,但其实现其实比list要更复杂。slist不在标准规则之内。

对于STL习惯来说,插入操作往往是在指定位置前插入,这对slist来说效率太低,因此只提供了insert_after()和erase_after()。

由于slist不方便插入和删除,其不提供push_back(),只提供push_front()。

节点

slist的节点和迭代器的设计,比list复杂很多,运用了继承机制,因此在类型转换上表现地更为复杂。

 迭代器相关代码:

struct _Slist_iterator_base
{
  typedef size_t               size_type;
  typedef ptrdiff_t            difference_type;
  typedef forward_iterator_tag iterator_category;

  _Slist_node_base* _M_node;   // 指向slist的节点

  _Slist_iterator_base(_Slist_node_base* __x) : _M_node(__x) {}
  void _M_incr() { _M_node = _M_node->_M_next; }

  // 这两个函数在遍历是会用到,两个迭代器是否相等,就看其节点是否相等
  // 节点并未重载 ==,也就是指向同一个节点才相等
  bool operator==(const _Slist_iterator_base& __x) const {
    return _M_node == __x._M_node;
  }
  bool operator!=(const _Slist_iterator_base& __x) const {
    return _M_node != __x._M_node;
  }
};

template <class _Tp, class _Ref, class _Ptr>
struct _Slist_iterator : public _Slist_iterator_base
{
  typedef _Slist_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Slist_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
  typedef _Slist_iterator<_Tp, _Ref, _Ptr>             _Self;

  typedef _Tp              value_type;
  typedef _Ptr             pointer;
  typedef _Ref             reference;
  typedef _Slist_node<_Tp> _Node;
 ......
  // 不提供 --的运算符重载,不允许后退
};

slist遍历

这里就是会用到 重载符 ==,迭代器==的判断其实就是slist_node_base的比较。

对于end()

  iterator end() { return iterator(0); }

 构建一个临时迭代器对象,同时生成一个_slist_node_base类的临时节点node(0),

对于单链表最后一个节点,其next值本身也指向_slist_node_base类的node(0)

_Node* _M_create_node(const value_type& __x) {
    _Node* __node = this->_M_get_node();
    __STL_TRY {
      construct(&__node->_M_data, __x);
      __node->_M_next = 0;
    }

有点困惑,虽然node里都只有一个值next,且next的值相同,但两个node(0)的地址不同,_slist_node_base又没重载 ==,应该是不相等啊。

侯捷老师的书上也说明了这点

不过这里也体现出继承的一个好处吧,next和data分离,能更简单地判断当前迭代器是不是end()