前言
相较于 vector 的连续线性空间,list 就显得复杂许多、它的好处是每次插人或删除一个元素,就配置或释放一个元素空间。因此,1ist 对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素移除,list 永远是常数时间。
———— 《STL源码剖析》侯捷
本篇博客将会分析 STL 中 list
的结构定义部分。值得注意的是,在原始的 MyTinySTL
仓库中,作者 list 部分的实现方式是有些怪异的,包括但不限于:使用了本该出现在 SGI-STL 的 slist 中的 base_ptr
与 node_ptr
来代表 list 的指针、没有实现 transfer
函数、sort
的实现也很繁杂;保险起见,下面的解析均以 SGI-STL 中的实现为准。
list
SGI-STL 中的 list 是一个 双向环状链表 。对删除、插入的时间复杂度为O ( 1 ) O(1) O ( 1 ) ,效率相当高;但是随机访问的时间复杂度为O ( n ) O(n) O ( n ) 。另外,list
在插入和删除操作后迭代器并不会失效。
__list_node
以下是 list 的节点结构。
1 2 3 4 5 6 7 8 9 template <class T > struct __list_node { typedef void * void_pointer; void_pointer next; void_pointer prev; T data; };
__list_iterator
list 不再能够像 vector 一样以普通指针作为迭代器,因为其节点不保证在储存空间中连续存在。list 迭代器必须有能力指向 list 的节点,并有能力进行正确的递增、递减、取值、成员存取等操作。所谓 “1ist 迭代器正确的递增、递减、取值、成员取用” 操作是指,递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取用的是节点的成员。
由于 list 是一个双向链表 ( double linked-list ),迭代器必须具备前移、后移的能力,所以 list 提供的是 Bidirectional Iterators。
型别定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <class T , class Ref , class Ptr > struct __list_iterator { typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator; typedef __list_iterator<T, Ref, Ptr> self; typedef bidirectional_iterator_tag iterator_category; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef size_t size_type; typedef ptrdiff_t difference_type; ... };
构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 template <class T , class Ref , class Ptr > struct __list_iterator { ... typedef __list_node<T>* link_type; link_type node; __list_iterator(link_type x) : node (x) {} __list_iterator() {} __list_iterator(const iterator& x) : node (x.node) {} ... };
操作符重载
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 template <class T , class Ref , class Ptr > struct __list_iterator { ... bool operator ==(const self& x) const { return node == x.node; } bool operator !=(const self& x) const { return node != x.node; } reference operator *() const { return (*node).data; } pointer operator ->() const { return &(operator *()); } self& operator ++() { node = (link_type)((*node).next); return *this ; } self operator ++(int ) { self tmp = *this ; ++*this ; return tmp; } self& operator --() { node = (link_type)((*node).prev); return *this ; } self operator --(int ) { self tmp = *this ; --*this ; return tmp; } };
可以看到,由于 list 为双向链表,需要其迭代器重载 operator--()
并且也很好实现,直接利用 node->prev
就行。但 SGI-STL 中的另一种链表 slist ( single list ),就不支持 operator--()
,因为内部仅有 next 指针。
list 的结构
实现过 list 的人都知道,list 的节点与 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 template <class T , class Alloc = alloc>class list { protected : typedef void * void_pointer; typedef __list_node<T> list_node; typedef simple_alloc<list_node, Alloc> list_node_allocator; public : typedef T 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* link_type; typedef size_t size_type; typedef ptrdiff_t difference_type; protected : link_type node; public : typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator; ... };
这里的 node 节点指针是 list 结构的核心,可以代表整个 list。如果让指针 node 指向刻意置于尾端的一个空白节点,node 便能符合STL 对于 “前闭后开” 区间的要求,成为 last 迭代器。这么一来,以下几个函数便都可以轻易完成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 iterator begin () { return (link_type)((*node).next); }iterator end () { return node; }bool empty () const { return node->next == node; }size_type size () const { size_type res = 0 ; distance (begin (), end (), res); return res; } reference front () { return *begin (); }reference back () { return *(--end ()); }