stl源码分析之list

本文主要分析gcc4.8版本的stl list的源码实现,与vector的线性空间结构不同,list的节点是任意分散的,节点之间通过指针连接,好处是在任何位置插入删除元素都只需要常数时间,缺点是不能随机访问,查询复杂度是O(n),n为list中的元素个数。所以list非常适合应用与数据插入删除频繁的场景。

一、 list节点

list节点定义如下,

struct _List_node_base
{
    _List_node_base* _M_next;
    _List_node_base* _M_prev;
}
template<typename _Tp>
struct _List_node : public __detail::_List_node_base
{
    _Tp _M_data;
}

很明显list是一个双向链表,_M_next和_M_prev分别指向下一个和上一个节点,_M_data是节点存储的数据。

二、 list的迭代器

由于list不是线性空间结构,通用迭代器无法正常移动,所以list需要定义专门的迭代器。

template<typename _Tp>
struct _List_iterator
{
      typedef _List_iterator<_Tp>                _Self;
      typedef _List_node<_Tp>                    _Node;

      typedef ptrdiff_t                          difference_type;
      typedef std::bidirectional_iterator_tag    iterator_category;
      typedef _Tp                                value_type;
      typedef _Tp*                               pointer;
      typedef _Tp&                               reference;

      _List_iterator()
      : _M_node() { }

      explicit
      _List_iterator(__detail::_List_node_base* __x)
      : _M_node(__x) { }

      // Must downcast from _List_node_base to _List_node to get to _M_data.
      reference
      operator*() const
      { return static_cast<_Node*>(_M_node)->_M_data; }

      pointer
      operator->() const
      { return std::__addressof(static_cast<_Node*>(_M_node)->_M_data); }

      _Self&
      operator++()
      {
        _M_node = _M_node->_M_next;
        return *this;
      }

      _Self
      operator++(int)
      {
        _Self __tmp = *this;
        _M_node = _M_node->_M_next;
      return __tmp;
      }

      _Self&
      operator--()
      {
        _M_node = _M_node->_M_prev;
        return *this;
      }

      _Self
      operator--(int)
      {
        _Self __tmp = *this;
        _M_node = _M_node->_M_prev;
        return __tmp;
      }
      // 指向list节点指针
      __detail::_List_node_base* _M_node;
};

list迭代器指向list的node,所以解引用时必须返回 static_cast<_Node*>(_M_node)->_M_data。list迭代器不是随机访问迭代器,必须通过节点的next和prev指针一步步移动,元素访问复杂度是线性的。另外还定义了只读迭代器_List_const_iterator。

三、 list的定义

和vector一样,list继承于_List_base,内存分配释放工作由_List_base负责,

template<typename _Tp, typename _Alloc>
class _List_base
{
    protected:
//使用traits方法,得到_List_node<_Tp>的内存分配器
 typedef typename _Alloc::template rebind<_List_node<_Tp> >::other
        _Node_alloc_type;
      typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;

struct _List_impl
      : public _Node_alloc_type
      {
      //数据成员就只有一个头节点
        __detail::_List_node_base _M_node;
        _List_impl()
        : _Node_alloc_type(), _M_node()
        { }
        _List_impl(const _Node_alloc_type& __a)
        : _Node_alloc_type(__a), _M_node() { }

      };
      _List_impl _M_impl;
//分配节点的接口
  _List_node<_Tp>* _M_get_node()
      { return _M_impl._Node_alloc_type::allocate(1); }
  _List_base()
      : _M_impl()
      { _M_init(); }
//list初始为空,头节点的next和prev都指向自身
 void _M_init()
      {
        this->_M_impl._M_node._M_next = &this->_M_impl._M_node;
        this->_M_impl._M_node._M_prev = &this->_M_impl._M_node;
      }
//从第一个数据节点开始依次删除并释放内存。
  template<typename _Tp, typename _Alloc>
    void  _List_base<_Tp, _Alloc>:: _M_clear()
    {
      typedef _List_node<_Tp>  _Node;
      _Node* __cur = static_cast<_Node*>(_M_impl._M_node._M_next);
      while (__cur != &_M_impl._M_node)
        {
          _Node* __tmp = __cur;
          __cur = static_cast<_Node*>(__cur->_M_next);
          _M_get_Node_allocator().destroy(__tmp);
          _M_put_node(__tmp);
        }
}

list继承了_List_base的头节点,

template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class list : protected _List_base<_Tp, _Alloc>
{
      // concept requirements
      typedef typename _Alloc::value_type                _Alloc_value_type;
      __glibcxx_class_requires(_Tp, _SGIAssignableConcept)
      __glibcxx_class_requires2(_Tp, _Alloc_value_type, _SameTypeConcept)

      typedef _List_base<_Tp, _Alloc>                    _Base;
      typedef typename _Base::_Tp_alloc_type             _Tp_alloc_type;
      typedef typename _Base::_Node_alloc_type           _Node_alloc_type;

    public:
      typedef _Tp                                        value_type;
      typedef typename _Tp_alloc_type::pointer           pointer;
      typedef typename _Tp_alloc_type::const_pointer     const_pointer;
      typedef typename _Tp_alloc_type::reference         reference;
      typedef typename _Tp_alloc_type::const_reference   const_reference;
      typedef _List_iterator<_Tp>                        iterator;
      typedef _List_const_iterator<_Tp>                  const_iterator;
      typedef std::reverse_iterator<const_iterator>      const_reverse_iterator;
      typedef std::reverse_iterator<iterator>            reverse_iterator;
      typedef size_t                                     size_type;
      typedef ptrdiff_t                                  difference_type;
      typedef _Alloc                                     allocator_type;
......

四、 list构造方式

list提供了多种构造函数,

//默认构造函数
  list()
      : _Base() { }
//初始化为n个默认值
  explicit
      list(size_type __n)
      : _Base()
      { _M_default_initialize(__n); }
//初始化为n个__value值
list(size_type __n, const value_type& __value,
           const allocator_type& __a = allocator_type())
      : _Base(_Node_alloc_type(__a))
      { _M_fill_initialize(__n, __value); }
//拷贝构造函数
  list(const list& __x)
      : _Base(__x._M_get_Node_allocator())
      { _M_initialize_dispatch(__x.begin(), __x.end(), __false_type()); }
//移动构造函数
  list(list&& __x) noexcept
      : _Base(std::move(__x)) { }

五、 list元素操作

篇幅有限,只讨论部分函数。

iterator  begin() _GLIBCXX_NOEXCEPT
{ return iterator(this->_M_impl._M_node._M_next); }
 iterator end() _GLIBCXX_NOEXCEPT
{ return iterator(&this->_M_impl._M_node); }
end指向头节点,begin指向第一个数据节点,begin和end仍然组成一个前闭后开区间。
Bool  empty() const _GLIBCXX_NOEXCEPT
{ return this->_M_impl._M_node._M_next == &this->_M_impl._M_node; }
通过查看头节点是否指向自身来判断list是否为空。
void push_front(const value_type& __x)
{ this->_M_insert(begin(), __x); }
void push_back(const value_type& __x)
{ this->_M_insert(end(), __x); }
template<typename... _Args> void _M_insert(iterator __position, _Args&&... __args)
{
     _Node* __tmp = _M_create_node(std::forward<_Args>(__args)...);
     __tmp->_M_hook(__position._M_node);
}
template<typename _Tp, typename _Alloc>
 typename list<_Tp, _Alloc>::iterator
 list<_Tp, _Alloc>::
insert(iterator __position, const value_type& __x)
{
    _Node* __tmp = _M_create_node(__x);
    __tmp->_M_hook(__position._M_node);
    return iterator(__tmp);
}

push_front,push_back和insert的时间复杂度都是O(1).

template<typename _Tp, typename _Alloc>
void list<_Tp, _Alloc>::merge(list&& __x)
{
      if (this != &__x)
        {
          _M_check_equal_allocators(__x); 

          iterator __first1 = begin();
          iterator __last1 = end();
          iterator __first2 = __x.begin();
          iterator __last2 = __x.end();
          while (__first1 != __last1 && __first2 != __last2)
            if (*__first2 < *__first1)
              {
                iterator __next = __first2;
                _M_transfer(__first1, __first2, ++__next);
                __first2 = __next;
              }
            else
              ++__first1;
          if (__first2 != __last2)
            _M_transfer(__last1, __first2, __last2);
        }
}

stl源码分析之list,布布扣,bubuko.com

时间: 2024-08-04 07:56:54

stl源码分析之list的相关文章

stl源码分析之hash table

本文主要分析g++ stl中哈希表的实现方法.stl中,除了以红黑树为底层存储结构的map和set,还有用哈希表实现的hash_map和hash_set.map和set的查询时间是对数级的,而hash_map和hash_set更快,可以达到常数级,不过哈希表需要更多内存空间,属于以空间换时间的用法,而且选择一个好的哈希函数也不那么容易. 一. 哈希表基本概念 哈希表,又名散列表,是根据关键字直接访问内存的数据结构.通过哈希函数,将键值映射转换成数组中的位置,就可以在O(1)的时间内访问到数据.举

STL源码分析--仿函数 &amp; 配接器

STL源码分析-仿函数 & 配接器 仿函数就是函数对象.就实现观点而言,仿函数其实就是一个"行为类似函数"的对象.为了能够"行为类似函数",其类别定义中必须自定义(或说改写.重载)function call 运算子(operator()),拥有这样的运算子后,我们就可以在仿函数的对象后面加上一对小括号,以此调用仿函数所定义的operator().仿函数作为可配接的关键因素. 配接器在STL组件的灵活组合运用功能上,扮演着轴承.转换器的角色,adapter的定

stl源码分析之priority queue

前面两篇介绍了gcc4.8的vector和list的源码实现,这是stl最常用了两种序列式容器.除了容器之外,stl还提供了一种借助容器实现特殊操作的组件,谓之适配器,比如stack,queue,priority queue等,本文就介绍gcc4.8的priority queue的源码实现. 顾名思义,priority queue是带有优先级的队列,所以元素必须提供<操作符,与vector和list不同,priority queue允许加入元素,但是取出时只能取出优先级最高的元素. 一. pri

stl源码分析之vector

上篇简单介绍了gcc4.8提供的几种allocator的实现方法和作用,这是所有stl组件的基础,容器必须通过allocator申请分配内存和释放内存,至于底层是直接分配释放内存还是使用内存池等方法就不是组件需要考虑的事情.这篇文章开始分析gcc4.8 stl的容器源码实现.stl的容器分为序列式容器和关联式容器,前者包括vector,list,queue以及stack等常用数据结构,后者包含了map,set以及hash table等比较高级的结构,本文就从使用最广泛也是最基础的vector开始

STL源码分析--空间配置器的底层实现 (二)

STL源码分析-空间配置器 空间配置器中门道 在STL中的容器里都是使用统一的空间配置器,空间配置器就是管理分配内存和销毁内存的.在STL将在heap空间创建一个对象分为两个步骤,第一是申请一块内存,第二是在这块内存中初始化一个对象.首先申请空间是由malloc提供,初始化一个对象时由constructor管理.销毁一个对象也是由两步骤完成,第一是销毁空间上的对象,第二是释放这块内存. 同时,STL的空间配置器分为两级内存,如果申请的内存空间大于128KB,那么就使用第一级空间配置,如果小于,那

STL源码分析--仿函数 &amp; 模板的模板参数 &amp; 临时对象

STL源码分析-使用的一些特殊语法 关于泛型编程中用到的一些特殊语法,这些语法也适用于平常的模板编程 1.  类模板中使用静态成员变量 Static成员变量在类模板中并不是很特殊,同时这个变量不属于对象,属于实例化以后的这个类类型.每一个实例化对应一个static变量 2.  类模板中可以再有模板成员 3.  模板参数可以根据前一个模板参数而设定默认值 4.  类模板可以拥有非类型的模板参数 所谓非类型的模板参数就是内建型的模板参数 Template <class T,class Alloc =

STL 源码分析 # stl_iterator &amp; stl_iterator_base #

STL 源码分析 # stl_iterator_base && stl_iterator # 这里能很清楚的看到各个基础类型的继承关系 template <class _Tp, class _Distance> struct input_iterator { typedef input_iterator_tag iterator_category; typedef _Tp value_type; typedef _Distance difference_type; typede

STL源码分析--萃取编程(traits)技术的实现

1.为什么要出现? 按照默认认定,一个模板给出了一个单一的定义,可以用于用户可以想到的任何模板参数!但是对于写模板的人而言,这种方式并不灵活,特别是遇到模板参数为指针时,若想实现与类型的参量不一样的实例化,就变得不太可能了!也有时,想禁止此种相同的实例化变得不太可能!故而出现了,Partial Specialization! 同时,在使用void*指针时,可以最大限度的共享代码,减少代码的膨胀! 2.它是什么?其实,就是用户定义的偏特化.用template<>来说明这是一个偏特化,针对任何模板

STL源码分析--算法

STL源码剖析-算法 在STL中的算法中一些算法是可以根据算法名字来判断算法作用的.所有算法的参数都是迭代器,不过不同的算法调用的迭代器类型也是不同的.多有的STL算法都作用在由迭代器{first,lase)所表示出来的区间上.拷贝(copy)交换(swap)替换(replace)填写(fill)删除(remove)排列组合(permutation)分割(partition)随机重排(random shuffling)排序(sort)计算(accumulate)计数(count)匹配(searc