首页 > STL源码剖析 > STL笔记之list

STL笔记之list

STL提供的list是一个双向链表容器,对应的迭代器类型为Bidirectional Iterators. 对于双向链表我们可以方便的在任意位置进行插入和删除操作,list每个节点的内存位置之间没有必然联系。

1. list 迭代器
链表由许多节点链接在一起构成,list中的节点的定义如下:

// ListNodeBase定义
struct _List_node_base {
  _List_node_base* _M_next;
  _List_node_base* _M_prev;
};
 
// ListNode定义
template <class _Tp>
struct _List_node : public _List_node_base {
  _Tp _M_data;  // 数据域
};

list的迭代器定义如下(主要是迭代器的类型说明,以及遍历操作++/–的实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// List Iterator Base 定义
struct _List_iterator_base {
  typedef size_t                     size_type;
  typedef ptrdiff_t                  difference_type;
  // 迭代器类型为 bidirectional_iterator_tag
  typedef bidirectional_iterator_tag iterator_category;
  // 指向的ListNode
  _List_node_base* _M_node;
  // 构造函数
  _List_iterator_base(_List_node_base* __x) : _M_node(__x) {}
  _List_iterator_base() {}
  // 前进与后退操作
  void _M_incr() { _M_node = _M_node->_M_next; }
  void _M_decr() { _M_node = _M_node->_M_prev; }
  // ==以及!=操作符
  bool operator==(const _List_iterator_base& __x) const {
    return _M_node == __x._M_node;
  }
  bool operator!=(const _List_iterator_base& __x) const {
    return _M_node != __x._M_node;
  }
};  
 
// List Iterator 定义
template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator : public _List_iterator_base {
  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;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef _List_node<_Tp> _Node;
 
  _List_iterator(_Node* __x) : _List_iterator_base(__x) {}
  _List_iterator() {}
  _List_iterator(const iterator& __x) : _List_iterator_base(__x._M_node) {}
 
  // 解引用操作
  reference operator*() const { return ((_Node*) _M_node)->_M_data; }
  // -> 操作, 基于解引用操作实现
  pointer operator->() const { return &(operator*()); }
 
  // 前置自增操作符
  _Self& operator++() { 
    this->_M_incr();
    return *this;
  }
  // 后置自增操作符
  _Self operator++(int) { 
    _Self __tmp = *this;
    this->_M_incr();
    return __tmp;
  }
  // 前置自减从操作符
  _Self& operator--() { 
    this->_M_decr();
    return *this;
  }
  // 后置自减操作符
  _Self operator--(int) { 
    _Self __tmp = *this;
    this->_M_decr();
    return __tmp;
  }
};

2. list 基本数据结构
list的基本数据结构定义于_List_base之中,里面包含了节点空间的分配和回收操作(get_node/put_node),以及构造函数与析构函数,在析构函数中进行了clear操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// List Base 定义
// _Alloc 使用标准空间配置器
template <class _Tp, class _Alloc>
class _List_base 
{
public:
  typedef _Alloc allocator_type;
  allocator_type get_allocator() const { return allocator_type(); }
 
  // 构造函数
  _List_base(const allocator_type&) {
    _M_node = _M_get_node();    // 分配一个节点的空间
    _M_node->_M_next = _M_node; // next 指针指向自己
    _M_node->_M_prev = _M_node; // prev 指针指向自己
  }
  // 析构函数
  ~_List_base() {
    clear();                    // 清空所有节点
    _M_put_node(_M_node);       // 回收节点的空间
  }
 
  void clear();
 
protected:
  typedef simple_alloc<_List_node<_Tp>, _Alloc> _Alloc_type;
  // 分配一个节点的空间
  _List_node<_Tp>* _M_get_node() { return _Alloc_type::allocate(1); }
  // 回收一个节点的空间
  void _M_put_node(_List_node<_Tp>* __p) { _Alloc_type::deallocate(__p, 1); } 
 
protected:
  // 定义一个指针, 指向end()
  _List_node<_Tp>* _M_node;
};
 
// clear 函数
template <class _Tp, class _Alloc>
void 
_List_base<_Tp,_Alloc>::clear() 
{
  // 取得指向第一个节点的指针
  _List_node<_Tp>* __cur = (_List_node<_Tp>*) _M_node->_M_next;
  while (__cur != _M_node) {
    // 取出当前节点
    _List_node<_Tp>* __tmp = __cur;
    __cur = (_List_node<_Tp>*) __cur->_M_next;
    // 析构节点所携带的对象
    _Destroy(&__tmp->_M_data);
    // 回收节点的空间
    _M_put_node(__tmp);
  }
  // 修正节点指针
  _M_node->_M_next = _M_node;
  _M_node->_M_prev = _M_node;
}

list中定义了一个指针_M_node,它指向一个特殊的list节点(即链表末尾的空白节点),list可以通过_M_node来遍历其他所有节点。在构造函数中可以看到_M_node的next指针和prev指针都是指向自己的,事实上,_M_node即为end(),其next指针指向真正的起始数据节点,基于_M_node可以方便的实现很多函数(定义于list内部):

  iterator begin()      { return (_Node*)(_M_node->_M_next); }
  iterator end()        { return _M_node; }
  bool empty() const    { return _M_node->_M_next == _M_node; }
  size_type size() const{
    size_type __result = 0;
    distance(begin(), end(), __result);
    return __result;
  }
  reference front()     { return *begin(); }
  reference back()      { return *(--end()); }

list是一个环状的双向链表,如图:(图片来源于《STL源码剖析》,已修改)
STL list环状链表表示结构

3. list 基本操作的实现
这里是list内部一些基本操作的实现,其中transfer是一个比较重要的操作,因为很多其他操作都基于transfer来实现,而且基于这个操作也很好理解函数的意义。transfer是一个protected member function,其对外的包装接口为splice函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class list : protected _List_base<_Tp, _Alloc> {
public:
  // ==========================================================================
  // 两个链表进行交换操作
  // ==========================================================================
  // swap操作,通过全局的swap实现,直接交换两个链表的_M_node指针
  void swap(list<_Tp, _Alloc>& __x) { __STD::swap(_M_node, __x._M_node); }
 
  // ==========================================================================
  // 元素插入操作
  // ==========================================================================
  // 在指定位置插入一个值
  iterator insert(iterator __position, const _Tp& __x) {
    // 构造一个节点
    _Node* __tmp = _M_create_node(__x);
    // 插入节点到position位置
    __tmp->_M_next = __position._M_node;
    __tmp->_M_prev = __position._M_node->_M_prev;
    __position._M_node->_M_prev->_M_next = __tmp;
    __position._M_node->_M_prev = __tmp;
    // 返回指向新插入的节点
    return __tmp;
  }
 
  // 在指定位置插入一个节点,节点由默认构造函数负责初始化
  iterator insert(iterator __position) { return insert(__position, _Tp()); }
 
  // 在链表头插入元素
  void push_front(const _Tp& __x) { insert(begin(), __x); }
  void push_front() {insert(begin());}
  // 在链表尾部插入元素
  void push_back(const _Tp& __x) { insert(end(), __x); }
  void push_back() {insert(end());}
 
  // ==========================================================================
  // 元素删除操作
  // ==========================================================================
  // 删除指定位置的元素
  iterator erase(iterator __position) {
    _List_node_base* __next_node = __position._M_node->_M_next;
    _List_node_base* __prev_node = __position._M_node->_M_prev;
    _Node* __n = (_Node*) __position._M_node;
    __prev_node->_M_next = __next_node;
    __next_node->_M_prev = __prev_node;
    _Destroy(&__n->_M_data);
    _M_put_node(__n);
    // 返回指向被删除节点的下一个节点
    return iterator((_Node*) __next_node);
  }
  // 清空操作,调用父类的clear实现
  void clear() { _Base::clear(); }
 
  // resize 操作
  void resize(size_type __new_size, const _Tp& __x)
  {
    iterator __i = begin();
    size_type __len = 0;
    for ( ; __i != end() && __len < __new_size; ++__i, ++__len)
      ;
    // 如果 len == new_size 说明链表后面还有元素,那么这些元素需要删掉
    if (__len == __new_size)
      erase(__i, end());
    // 否则,说明链表的节点数没有 new_size 这么多,需要在末尾新插入元素
    else                          // __i == end()
      insert(end(), __new_size - __len, __x);
  }
  void resize(size_type __new_size) { this->resize(__new_size, _Tp()); }
 
  // pop 操作
  void pop_front() { erase(begin()); }
  void pop_back() { 
    iterator __tmp = end();
    erase(--__tmp);
  }
 
protected:
  // ==========================================================================
  // 元素转移操作
  // ==========================================================================
  // 把[first, last)内的所有元素移动到position之前
  // 区间不可重叠,即position不能位于[first, last)区间内部
  void transfer(iterator __position, iterator __first, iterator __last) {
    if (__position != __last) {
      // Remove [first, last) from its old position.
      __last._M_node->_M_prev->_M_next     = __position._M_node;
      __first._M_node->_M_prev->_M_next    = __last._M_node;
      __position._M_node->_M_prev->_M_next = __first._M_node; 
 
      // Splice [first, last) into its new position.
      _List_node_base* __tmp      = __position._M_node->_M_prev;
      __position._M_node->_M_prev = __last._M_node->_M_prev;
      __last._M_node->_M_prev     = __first._M_node->_M_prev; 
      __first._M_node->_M_prev    = __tmp;
    }
  }
 
public:
  // 把链表x的节点转移到position位置
  void splice(iterator __position, list& __x) {
    if (!__x.empty()) 
      this->transfer(__position, __x.begin(), __x.end());
  }
 
  // 将i所指元素移动到position
  void splice(iterator __position, list&, iterator __i) {
    iterator __j = __i;
    ++__j;
    // 如果i和position指向同一个元素,则不需要进行任何操作
    // 如果i所指节点就是位于position所指节点的前面一个节点,也不需要进行任何操作
    if (__position == __i || __position == __j) return;
    // 将区间[i, j)移动到position
    this->transfer(__position, __i, __j);
  }
 
  // 对transfer的简单包装,区间不可重叠
  void splice(iterator __position, list&, iterator __first, iterator __last) {
    if (__first != __last) 
      this->transfer(__position, __first, __last);
  }
 
  // 删除具有指定值的元素
  void remove(const _Tp& __value)
  {
    iterator __first = begin();
    iterator __last = end();
    while (__first != __last) {
      iterator __next = __first;
      ++__next;
      if (*__first == __value) erase(__first);
      __first = __next;
    }
  }
 
  // 如果连续的几个元素具有相同的值,则删除相同的,只留一个
  void unique()
  {
    iterator __first = begin();
    iterator __last = end();
    if (__first == __last) return;
    iterator __next = __first;
    while (++__next != __last) {
      if (*__first == *__next)
        erase(__next);
      else
        __first = __next;
        __next = __first;
    }
  }
 
  // 合并两个已经排好序的链表, 默认从小到大排序
  // 基于transfer实现,把x中的节点转移到本链表适当的位置
  void merge(list& __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;
        // 基于transfer实现
        transfer(__first1, __first2, ++__next);
        __first2 = __next;
      }
      else
        ++__first1;
    if (__first2 != __last2) transfer(__last1, __first2, __last2);
  }
};

4. reverse与sort
《STL源码剖析》一书中reverse是通过调用transfer来实现的,就是顺序遍历链表,然后不断的把元素插入的begin()之前,这个过程还是非常好理解的,源代码如下:
《STL源码剖析》list reverse实现(基于transfer)

但我实际找到的SGI STL的代码并不是基于transfer实现的,而是通过遍历链表的时候,不断的交换节点的next和prev指针来实现的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class list
{
public:
  // 翻转链表
  void reverse()
  {
    __List_base_reverse(this->_M_node);
  }
  // ...
}
 
// 翻转链表的实现
inline void __List_base_reverse(_List_node_base* __p)
{
  _List_node_base* __tmp = __p;
  do {
    __STD::swap(__tmp->_M_next, __tmp->_M_prev);
    __tmp = __tmp->_M_prev;     // Old next node is now prev.
  } while (__tmp != __p);
}

这个实现过程画个图的话也是很好理解的:
基于swap next和prev指针实现list翻转

list由于自身的特性(bidirectional iterator),显然不能够试用std::sort,STL提供了list的一个member function,用于排序,sort的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class list
{
public:
  // 排序操作
  void sort()
  {
    // 只有链表的长度大于等于2才进行排序操作
    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.splice(__carry.begin(), *this, begin());
        int __i = 0;
        while(__i < __fill && !__counter[__i].empty()) {
          __counter[__i].merge(__carry);
          __carry.swap(__counter[__i++]);
        }
        __carry.swap(__counter[__i]);         
        if (__i == __fill) ++__fill;
      } 
 
      for (int __i = 1; __i < __fill; ++__i)
        __counter[__i].merge(__counter[__i-1]);
      swap(__counter[__fill-1]);
    }
  }
}

这段代码并不容易理解,需要在脑袋里面模拟执行几遍才好。为了更好的理解这个过程,可以用Visual Studio进行跟踪一下,虽然代码风格/实现不太一致,不过过程还是一样的。我所理解的是,counter数组的每个元素可存储的元素个数为2^i个,如:

counter[0] 最多存储 1 个元素
counter[1] 最多存储 2 个元素
counter[2] 最多存储 4 个元素
counter[3] 最多存储 8 个元素
...

在VS中跟踪时的监控值如下:
在VS中跟踪list sort实现

对sort过程的伪代码理解如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
list::sort():
    if 长度 < 2:
        return
    fill = 0
    carry       : list
    counter[64] : list
    while list非空:
        取出list的begin()节点并merge到carry
        # carry不断收集元素,直到收集完毕或者下一层为空
        for i in [0, fill) and counter[i]不为空:
            把carry合并到counter[i]
            交换carry与counter[i]
 
        # 此时carry已经合并了counter[0] 到 counter[i-1]的所有元素
        # 实际就是把carry的数据转交给下一层
        交换carry与counter[i]
        # 如果i == fill
        # 说明carry已经合并了counter[0] 到 counter[fill-1]的所有元素
        # 需要新增一层
        if i == fill:
            fill += 1
    # 所有元素处理完毕
    合并counter[0] 到 counter[fill-1]的所有元素
    将排序后的数据交换到list自身

这个过程关键还在于自己的理解。

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> STL笔记之list
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.