Bloddy 2019-10-20
较久以前学过数据结构,对链表的定义和行为结构有过了解,所以阅读源码学习stl定义的list容器的并不算吃力。
list与vector都是两个常用的容器,与vector不同,list不是连续线性空间的,list是一个双向链表。每次插入或者删除一个元素,将配置或者释放一个元素空间,因此,list对于空间的运用有着绝对的精准,不会造成浪费现象。而且对于任何位置的元素插入或者删除,其操作时间永远是常数时间。(缺点是不能进行随机的访问)
list链表本身和list节点是分开设计的,以下是list节点的结构
template <class T> struct __list_node { typedef void* void_pointer; void_pointer next; void_pointer prev; T data; };
可以看出list节点是一个双向链表,next指向下一个节点,prev指向前一个节点
学过数据结构就知道,链表的一个特点是其分配空间不必固定在一段连续的存储空间中,所以list不能像vector一样以普通指针作为迭代器,因为其节点不能保证在存储空间中连续存在。list迭代器必须有能力指向其list节点,并有能力进行正确的递增、递减、取值、成员存取等操作。
由于STL list是一个双向链表,迭代器必须具备前移、后移的能力,所以list提供的是Bidirectional Iterators (双向迭代器)。
在vector中,对其进行插入操作有可能会使其满载并且溢出,这样vector便需要重新配置空间,这样原有的迭代器会全部失效。而list在进行插入和接合操作都不会造成原有的list迭代器失效,即便是其删除操作,也只有“指向被删除元素”的迭代器失效,其他迭代器不受影响。
template<class T,class Ref,class Ptr> struct _list_iterator{ typedef _list_iterator<T,T&,T*> iterator; typedef _list_iterator<T,T&,T*> iterator; ? typedef bidirectional_iterator_tag iterator_category; typedef T value_type; typedef Ptr pointer; typedef Ref reference; typedef _list_node<T>* link_type; typedef size_t size_type; typedef ptrdiff_t difference_type; link_type node; _list_iterator(link_type x):node(x){} _list_iterator(){} _list_iterator(const iterator& x):node(x.node){} 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;} reference 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; } }
SGI list不仅是一个双向链表,而且是一个环状的双向链表。
template<class T,class Alloc = alloc> //缺省使用alloc为配置器 class list{ protected : typedef __list_node<T> list_node ; public : typedef list_node* link_type ; protected : link_type node ; //只要一个指针,便可以表示整个环状双向链表 ... };
如果让指针node指向刻意置于尾端的一个空白节点,node就能符合stl对于前闭后开区间的要求,成为last迭代器。这样以下函数便能轻易完成
iterator begin() { return (link_type)((*node).next); } iterator end() { return node; } bool empty() const { return node->next == node; } size_type size() const { size_type result = 0; distance(begin(), end(), result);//SGI里面的distance函数作用就是遍历链表,十分影响性能 return result; } reference front() { return *begin(); } reference back() { return *(--end()); }
list的构造方式有很多,这里列出缺省情况下的构造函数
/ 默认allocator为alloc template <class T, class Alloc = alloc> class list { ... public: list() { empty_initialize(); } protected: // 专属空间配置器,配置单位为一个节点大小 typedef simple_alloc<list_node, Alloc> list_node_allocator; // 建立空链表 void empty_initialize() { node = get_node(); node->next = node; node->prev = node; } // 配置一个节点,不进行构造 link_type get_node() { return list_node_allocator::allocate(); } // 释放一个节点, 不进行析构 void put_node(link_type p) { list_node_allocator::deallocate(p); } // 配置并构造一个节点 link_type create_node(const T& x) { link_type p = get_node(); construct(&p->data, x); return p; } // 析构并释放节点 void destroy_node(link_type p) { destroy(&p->data); put_node(p); } ... }
insetr()是一个重载函数,同样有很多种形式,其中最简单如下,首先配置并构造一个节点,然后在尾端进行适当指针操作插入新节点(push_back()和push_front插入新元素时都会调用insert())
iterator insert(iterator position, const T& x) { link_type tmp = create_node(x); // 产生一个节点 // 调整双向指针,使tmp插入 tmp->next = position.node; tmp->prev = position.node->prev; (link_type(position.node->prev))->next = tmp; position.node->prev = tmp; return tmp; }
list的元素操作很多,无论是简单的插入删除清除还是迁移,大部分都不难,下面只列出部分常用的操作,有兴趣可以详细阅读《stl源码剖析》第四章关于list的内容,或者直接下载源码来研究
void push_front(const T&x){ insert(begin(),x); } iterator erase(iterator position){ link_type next_node=link_type(position.node->next); link_type prev_node=link_type(position.node->prev_nodext); prev_node->next=next_node; next_node->prev=prev_node; destroy_node(position.node); return iterator(next_node); } void pop_front(){ erase(begin()); } void pop_back(){ iterator i=end(); erase(--i); } //将某连续范围的元素迁移到某个特定位置之前。技术上讲很简单,节点直接的指针移动而已。 void transfer(iterator position, iterator first, iterator last) { if (position != last) { (*(link_type((*last.node).prev))).next = position.node; (*(link_type((*first.node).prev))).next = last.node; (*(link_type((*position.node).prev))).next = first.node; link_type tmp = link_type((*position.node).prev); (*position.node).prev = (*last.node).prev; (*last.node).prev = (*first.node).prev; (*first.node).prev = tmp; } }