STL容器之list源码详解

简介

相对于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源码剖析》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值