《STL 源码剖析》 list 实现原理
list概述
list对空间的运用是精准的,不浪费的。对于任何位置的元素插入 或 元素移除,list永远是常数时间。
list实现上就是一个双向循环链表,list节点 有prev 和 next 两个指针。
list迭代器
因为list是一个双向链表,他的迭代器就必须具备前移、后移的能力。list提供的是 Bidirectional Iterators。
list性质:插入操作、接合操作,不会造成原有的list迭代器失效,即使删除操作,也只是 被指向删除元素的 那个迭代器失效,其他不受影响。(这个和vector 有很大差别)
list数据结构
list是一个 环状双向链表,因此它只需要一个指针就能够 表现出整个完整链表
template <class T, class Alloc = alloc>
class list
{
protected:
typedef __list_node<T> list_node;
public:
typedef list_node* link_type;
protected:
link_type node;// 只要一个指针,就表示整个链表
...
};
node指向尾端的一个空白节点,就能符合 ”前闭后开“ 区间的要求。
基本操作:
iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }
bool empty() const { return node->next == node; }
size_type size() const { // C++11 之前 复杂度线性,11之后复杂度 常数
size_type result = 0;
distance(begin(),end(),result); // stl 全局函数
return result;
}
reference front() { return *begin(); }
reference back() { return *(--end()); }
list 构造与内存管理
class list
{
protected:
// 专属的空间配置器,每次配置一个节点大小
typedef simple_alloc<list_node, Alloc> list_node_allocator;
// list_node_allocator(n) 标识配置n个节点空间
protected:
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 destory_node(link_type p) // 析构 并 释放空间
{
destory(&p->data); // 析构
put_node(p); // 释放
}
public:
list() { empty_initialize(); }// 构造空链表
void push_back(const T& x) { insert(end(), x); }
iterator insert(iterator position, const T& x)
{
link_type tmp = create_node(x);
tmp->next = position.node;
tmp->prev = position.node->prev;
(link_type(position.node->prev))->next = tmp;
position.node->prev = tmp;
return tmp;
}
protected:
void empty_initialize()
{
node = get_node();
node->next = node;
node->prev = node;
}
...
};
list 操作实现原理
- push_front、push_back、erase、pop_front、pop_back 这些操作 实现上 和 通常手写链表的实现 相同。
list的transfer(前移操作)
将连续范围的元素迁移到特定位置之前,实现上就是 节点指针的重新连接。
transfer 是 其他复杂操作的基础,如splice、sort、merge
transfer 不是公开接口
splice实现原理
对于transfer的巧妙运用
void splice(iterator position, list& x) //将 x 拼接到 position位置之前
{
if(!x.empty())
transfer(position, x.begin(), x.end());
}
void splice(iterator position, list&, iterator i) // 将i的元素拼接到position之前,两者 可能是同一个list
{
iterator j = i;
++j;
if(position == i || position == j) return;
transfer(position, i, j);
}
// 将[first, last)的元素接到position位置之前
// position 和 [first, last) 可能会指向同一个list
// 但是 position 不能位于[first,last)
void splice(iterator position, list&, iterator first, iterator last)
{
if(first != last)
transfer(position, first, last);
}
merge实现原理
前提:两个 已经 排好序的链表
遍历其中一个链表,记录过程中 遍历 头尾指针(遍历过程中进行大小比较)。将其拼接到另一个链表上。
实现上 和 普通链表合并 是类似的。
template <class T, class Alloc>
void list<T, Alloc>::merge(list<T, Alloc>& x)
{
iterator firts1 = begin();
iterator last1 = end();
iterator first2 = x.begin();
iterator last2 = x.end();
// 遍历处理
while(first1 ! last1 && first2 != last2)
{
if(*first2 < *first1)
{
iterator next = first2;
transfer(first1, first2, ++next);
first2 = next;
}
else
++first1;
}
if(first2!=last2) transfer(last1, first2, last2); // 如果末尾还有残留部分
}
list.sort 实现原理
- list不能使用STL的sort,是因为 STL 的 sort 只接受 RamdonAccessIterator (随机访问的迭代器类型),而list中是 Bidirectional (双向迭代器类型)
- list.sort实现原理,思想非常类似于 归并排序,但源码实现上 却不太好看出来 和 归并类似,需要自己手动模拟才能 有所体会。
- 其具体思想可查看 参考博客
- 下图是对应 该博客描述的 步骤 和 实现源码