SGISTL源码阅读十一 list容器上

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节点,它的prevnext都指向了它本身。

通过一下几个操作我们可以更清楚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节点,并让它的prevnext都指向它本身。

  • 初始化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_nodedestory_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的相关操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值