SGISTL源码阅读十一 list容器上
list概述
之前我们学习了连续线性空间的vector
,当容量不够用时它会简单粗暴地直接扩大到原来的两倍,难免会造成一定的空间浪费。
而list
就不一样,它的好处是每插入或者删除一个元素,就配置或释放一个元素空间。它对空间的使用绝对精准,一点也不浪费。而且在元素的插入删除操作上,时间复杂度是常数级别,对于vector
插入删除操作相当的麻烦。
深入源码
list
的数据结构及其迭代器
list
的节点
list
本身和它的节点不是同一个结构,是分开设计的。
通过以下代码我们不难看出,list
是一个双向链表。
template <class T>
struct __list_node {
typedef void* void_pointer; //
void_pointer next; //指向前一个节点
void_pointer prev; //指向后一个节点
T data;
};
list
的数据结构
其实list
除了是一个双向链表外,它还是一个双向循环链表,它使用了一个node
指针
template <class T, class Alloc = alloc>
class list {
protected:
typedef void* void_pointer;
typedef __list_node<T> list_node;
//...
protected:
link_type node; //list本身包含一个节点,用来表示整双向循环链表
//...
让node
指向尾端的一个空白节点,就能符合STL对于“前闭后开”区间的要求。
当list
为空时,仍然会存在一个node
节点,它的prev
和next
都指向了它本身。
通过一下几个操作我们可以更清楚list
的结构
//因为是双向循环链表,node指向尾端空节点,它的下一个节点便是头节点
iterator begin() { return (link_type)((*node).next); }
const_iterator begin() const { return (link_type)((*node).next); }
//node就是list的尾端
iterator end() { return node; }
const_iterator end() const { return node; }
//判断list是否为空,就看node节点的下一个节点是不是它本身(学习了list的构造你可能会更清楚)
bool empty() const { return node->next == node;}
//获取头节点元素
reference front() { return *begin(); }
const_reference front() const { return *begin(); }
//获取最后一个节点元素
reference back() { return *(--end()); }
如图所示
list
的迭代器
list
的数据结构使它不能像vector
那样简单地使用普通指针作为迭代器。
list
的迭代器必须能够指向list
的节点,并且能够进行正确的++、–、通过*取值等。
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;
//它的迭代器类型是bidirectional_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;
//内部维护的node节点,它的类型是list节点的指针类型
//这个node相当重要
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; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_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
迭代器的类型是bidirectional_iterator
,它具备前移、后移的能力,但是不能随机访问。
list
的构造及空间分配
list
的构造函数
- 默认构造函数
link_type get_node() { return list_node_allocator::allocate(); }
//...
void empty_initialize() {
node = get_node();
node->next = node;
node->prev = node;
}
//...
list() { empty_initialize(); }
我们可以看到默认构造函数的操作是创建一个node
节点,并让它的prev
和next
都指向它本身。
- 初始化n个节点的值
void fill_initialize(size_type n, const T& value){
//申请一个node并初始化它的指向
empty_initialize();
__STL_TRY {
//申请成功则以头插法插入进list
insert(begin(), n, value);
}
//申请失败则将所有节点全部销毁
__STL_UNWIND(clear(); put_node(node));
}
//...
//将n个节点的值都赋值为value
list(size_type n, const T& value) { fill_initialize(n, value); }
list(int n, const T& value) { fill_initialize(n, value); }
list(long n, const T& value) { fill_initialize(n, value); }
//将n个节点的值都赋值为默认值(explicit之前讲过,就是防止隐式转换)
explicit list(size_type n) { fill_initialize(n, T()); }
- 传入迭代器范围
#ifdef __STL_MEMBER_TEMPLATES
template <class InputIterator>
void range_initialize(InputIterator first, InputIterator last) {
//申请一个空白节点
empty_initialize();
__STL_TRY {
//头插法插入迭代器first,last指向范围的元素
insert(begin(), first, last);
}
//异常处理,销毁空间,防止内存泄露
__STL_UNWIND(clear(); put_node(node));
}
#else /* __STL_MEMBER_TEMPLATES */
//针对普通指针的重载版本
void range_initialize(const T* first, const T* last) {
empty_initialize();
__STL_TRY {
insert(begin(), first, last);
}
__STL_UNWIND(clear(); put_node(node));
}
//重载版本
void range_initialize(const_iterator first, const_iterator last) {
empty_initialize();
__STL_TRY {
insert(begin(), first, last);
}
__STL_UNWIND(clear(); put_node(node));
}
#endif /* __STL_MEMBER_TEMPLATES */
//...
template <class InputIterator>
list(InputIterator first, InputIterator last) {
range_initialize(first, last);
}
list(const T* first, const T* last) { range_initialize(first, last); }
list(const_iterator first, const_iterator last) {
range_initialize(first, last);
}
- 拷贝构造函数
list(const list<T, Alloc>& x) {
range_initialize(x.begin(), x.end());
}
list
的析构函数
~list() {
//调用clear删除所有list节点(除node外)
clear();
//销毁node(put_node负责删除某个节点)
put_node(node);
}
list
节点的申请和释放
在前面我们已经介绍了get_node
,申请一个节点。与get_node
所对应的是put_node
,删除一个节点
link_type get_node() { return list_node_allocator::allocate(); }
//直接销毁
void put_node(link_type p) { list_node_allocator::deallocate(p); }
其他的相关操作还有create_node
,destory_node
//申请并初始化该节点
link_type create_node(const T& x) {
//申请一个节点
link_type p = get_node();
__STL_TRY {
//构造该节点
construct(&p->data, x);
}
//异常处理
__STL_UNWIND(put_node(p));
return p;
}
//析构并释放该节点
void destroy_node(link_type p) {
destroy(&p->data); //全局函数,析构基本工具
//删除该节点
put_node(p);
}
总结
我们介绍了list
的数据结构,为一个双向循环链表,list
本身维护了一个node
,就算list
为空,也会存在一个node
节点。还介绍了list
的各种构造函数和空间分配的相关内容,后面我们将继续介绍list
的相关操作。