简介
相对于vector的连续线性空间,list就显得更加复杂,它每插入或者删除一个元素,就配置或释放一个元素空间,因此,list对于空间的利用非常精准,一点也不浪费,而且,对于任何位置的插入或者删除,list永远是常数时间。
构造函数
explicit list(const allocator_type& __a = allocator_type()) : _Base(__a) {}
// 构造拥有 n 个有值 value 的元素的容器
list(size_type __n, const _Tp& __value,
const allocator_type& __a = allocator_type())
: _Base(__a)
{ insert(begin(), __n, __value); }
explicit list(size_type __n)
: _Base(allocator_type())
{ insert(begin(), __n, _Tp()); }
// 构造拥有范围 [first, last) 内容的容器
list(const _Tp* __first, const _Tp* __last,
const allocator_type& __a = allocator_type())
: _Base(__a)
{ this->insert(begin(), __first, __last); }
list(const_iterator __first, const_iterator __last,
const allocator_type& __a = allocator_type())
: _Base(__a)
{ this->insert(begin(), __first, __last); }
// 拷贝构造函数
list(const list<_Tp, _Alloc>& __x) : _Base(__x.get_allocator())
{ insert(begin(), __x.begin(), __x.end()); }
对于list中的每一个节点,都被封装成了_List_node对象:
// 双向链表
struct _List_node_base {
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
// list 节点
template <class _Tp>
struct _List_node : public _List_node_base {
_Tp _M_data; // 节点存储的值
};
即list是通过双向循环链表来组织节点的。
主要函数
push_back
void push_back(const _Tp& __x) { insert(end(), __x); } // 插入一个节点,作为尾节点
// 在__position之前插入节点__x
iterator insert(iterator __position, const _Tp& __x) {
_Node* __tmp = _M_create_node(__x);
// 调整双向指针,插入新元素
__tmp->_M_next = __position._M_node; // list为双向链表
__tmp->_M_prev = __position._M_node->_M_prev;
__position._M_node->_M_prev->_M_next = __tmp;
__position._M_node->_M_prev = __tmp;
return __tmp;
}
push_back插入一个节点,作为尾结点,都是双向链表的常用操作,这里不再赘述。
push_front
void push_front(const _Tp& __x) { insert(begin(), __x); } // 插入一个节点 __x,作为头结点
iterator insert(iterator __position, const _Tp& __x) {
_Node* __tmp = _M_create_node(__x);
// 调整双向指针,插入新元素
__tmp->_M_next = __position._M_node; // list为双向链表
__tmp->_M_prev = __position._M_node->_M_prev;
__position._M_node->_M_prev->_M_next = __tmp;
__position._M_node->_M_prev = __tmp;
return __tmp;
}
push_front同样是调用了insert(iterator, const _Tp&)来完成插入操作的。
clear
void clear() { _Base::clear(); }
template <class _Tp, class _Alloc>
void
_List_base<_Tp,_Alloc>::clear()
{
_List_node<_Tp>* __cur = (_List_node<_Tp>*) _M_node->_M_next; // 指向开始节点,begin()
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_node->_M_next = _M_node;
_M_node->_M_prev = _M_node;
}
clear时从头结点到尾结点,依次释放每一个节点的内存空间。
特点
由于list是通过双向链表来实现的,它的迭代器要提供前移、后移的能力,所以list提供了Bidirectional iterator;
插入、删除节点的效率很高。
与vector的区别
- vector为存储的对象分配一块连续的地址空间,随机访问效率很高。但是插入和删除需要移动大量的数据,效率较低。尤其当vector中存储的对象较大,或者构造函数复杂,则在对现有的元素进行拷贝的时候会执行拷贝构造函数。
- list中的对象是离散的,随机访问需要遍历整个链表,访问效率比vector低。但是在list中插入元素,尤其在首尾插入,效率很高,只需要改变元素的指针。
- vector是单向的,而list是双向的;
- 向量中的iterator在使用后就释放了,但是链表list不同,它的迭代器在使用后还可以继续用;链表特有的;
参考资料
侯捷 《STL源码剖析》