list 容器

STL list 容器,又称双向链表容器,即该容器的底层是以双向链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

使用 list 容器的缺点是,它不能像 array 和 vector 那样,通过位置直接访问元素。举个例子,如果要访问 list 容器中的第 6 个元素,它不支持容器对象名[6]这种语法格式,正确的做法是从容器中第一个元素或最后一个元素开始遍历容器,直到找到该位置。

实际场景中,如何需要对序列进行大量添加或删除元素的操作,而直接访问元素的需求却很少,这种情况建议使用 list 容器存储序列。

list容器的创建

根据不同的使用场景,有以下 5 种创建 list 容器的方式供选择。

1) 创建一个没有任何元素的空 list 容器:

std::list<int> values;

和空 array 容器不同,空的 list 容器在创建之后仍可以添加元素,因此创建 list 容器的方式很常用。

2) 创建一个包含 n 个元素的 list 容器:

std::list<int> values(10);

通过此方式创建 values 容器,其中包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默认值为 0)。

3) 创建一个包含 n 个元素的 list 容器,并为每个元素指定初始值。例如:

std::list<int> values(10, 5);

如此就创建了一个包含 10 个元素并且值都为 5 个 values 容器。

4) 在已有 list 容器的情况下,通过拷贝该容器可以创建新的 list 容器。例如:

std::list<int> value1(10);
std::list<int> value2(value1);

注意,采用此方式,必须保证新旧容器存储的元素类型一致。

5) 通过拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器。例如:

//拷贝普通数组,创建list容器
int a[] = { 1,2,3,4,5 };
std::list<int> values(a, a+5);
//拷贝其它类型的容器,创建 list 容器
std::array<int, 5>arr{ 11,12,13,14,15 };
std::list<int>values(arr.begin()+2, arr.end());//拷贝arr容器中的{13,14,15}

list容器可用的成员函数

成员函数功能
begin()返回指向容器中第一个元素的双向迭代器。
end()返回指向容器中最后一个元素的双向迭代器。
rbegin()返回指向最后一个元素的反向双向迭代器。
rend()返回指向第一个元素所在位置前一个位置的反向双向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend()和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
empty()判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
size()返回当前容器实际包含的元素个数。
max_size()返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
front()返回第一个元素的引用。
back()返回最后一个元素的引用。
assign()用新元素替换容器中原有内容。
emplace_front()在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。
push_front()在容器头部插入一个元素。
pop_front()删除容器头部的一个元素。
emplace_back()在容器尾部直接生成一个元素。该函数和 push_back() 的功能相同,但效率更高。
push_back()在容器尾部插入一个元素。
pop_back()删除容器尾部的一个元素。
emplace()在容器中的指定位置插入元素。该函数和 insert() 功能相同,但效率更高。
insert() 在容器中的指定位置插入元素。
erase()删除容器中一个或某区域内的元素。
swap()交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。
resize()调整容器的大小。
clear()删除容器存储的所有元素。
splice()将一个 list 容器中的元素插入到另一个容器的指定位置。
remove(val)删除容器中所有等于 val 的元素。
remove_if()删除容器中满足条件的元素。
unique()删除容器中相邻的重复元素,只保留一个。
merge()合并两个事先已排好序的 list 容器,并且合并之后的 list 容器依然是有序的。
sort()通过更改容器中元素的位置,将它们进行排序。
reverse()反转容器中元素的顺序。

 

list源码剖析(SGI2.9版本)

list为双向环形链表,其结构为:

__list_node 用来实现节点,数据结构中就储存前后指针和属性。

template <class T>
struct __list_node {
  //前后指针
  typedef void* void_pointer;
  void_pointer next;
  void_pointer prev;
  //存储到节点中的值
  T data;
};

 来瞅一瞅,list 的节点长啥样,因为 list 是一种双向链表,所以基本结构就是下面这个样子:

 list的定义

template <class T, class Alloc = alloc>
class list {
protected:
  typedef void* void_pointer;
  typedef __list_node<T> list_node;   // 节点
  typedef simple_alloc<list_node, Alloc> list_node_allocator;   // 空间配置器
public:     
  // 定义嵌套类型
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef list_node* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

public:
  //定义迭代器
  typedef __list_iterator<T, T&, T*>             iterator;
  typedef __list_iterator<T, const T&, const T*> const_iterator;

  ...

protected:

  // 定义一个节点    它就是迭代器end()指向的空节点 它的.next指向begin() list初始化时分配空间
  link_type node;
}

list 构造和析构函数实现

构造函数前期准备:

每个构造函数都会创造一个空的 node 节点,为了保证我们在执行任何操作都不会修改迭代器。

list 默认使用 alloc 作为空间配置器,并根据这个另外定义了一个 list_node_allocator,目的是更加方便以节点大小来配置单元。

template <class T, class Alloc = alloc>
class list {
protected:
    typedef void* void_pointer;
    typedef __list_node<T> list_node; // 节点
    typedef simple_alloc<list_node, Alloc> list_node_allocator; // 空间配置器

其中,list_node_allocator(n)表示配置 n 个节点空间。以下四个函数,分别用来配置,释放,构造,销毁一个节点。

class list {
public:
  //默认构造函数
  list() { empty_initialize(); }

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();
    __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);
  }

protected:
  //初始化空列表
  void empty_initialize() { 
    node = get_node();
    node->next = node;
    node->prev = node;
  }
};

这里使用了2个全局的构造和析构函数,用已经申请到的指针去调用对象的构造和析构

template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

基本属性获取

template <class T, class Alloc = alloc>
class list {
    ...
public:
  //默认构造函数
  list() { empty_initialize(); }

  //返回第一个节点的迭代器
  iterator begin() { return (link_type)((*node).next); }
  const_iterator begin() const { return (link_type)((*node).next); }
  //返回最后一个元素的后一个元素的迭代器
  iterator end() { return node; }
  const_iterator end() const { return node; }

  reverse_iterator rbegin() { return reverse_iterator(end()); }
  const_reverse_iterator rbegin() const { 
    return const_reverse_iterator(end()); 
  }
  reverse_iterator rend() { return reverse_iterator(begin()); }
  const_reverse_iterator rend() const { 
    return const_reverse_iterator(begin());
  } 

  //判断是否为空链表, 这是判断只有一个空node来表示链表为空.
  bool empty() const { return node->next == node; }
  // 因为这个链表, 地址并不连续, 所以要自己迭代计算链表的长度.
  size_type size() const {
    size_type result = 0;
    distance(begin(), end(), result);
    return result;
  }

  size_type max_size() const { return size_type(-1); }

  // 返回第一个元素的值
  reference front() { return *begin(); }
  const_reference front() const { return *begin(); }
  // 返回最后一个元素的值
  reference back() { return *(--end()); }
  const_reference back() const { return *(--end()); }
}

list 的头插和尾插

template <class T, class Alloc = alloc>
class list {
    ...
  //最基本的insert操作, 之插入一个元素
  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;
  }

  //头插
  void push_front(const T& x) { insert(begin(), x); }
  //尾插
  void push_back(const T& x) { insert(end(), x); }

}

删除操作

删除元素的操作大都是由 erase 函数来实现的, 其他的所有函数都是直接或间接调用 erase。 list 是链表, 所以链表怎么实现删除, list 就在怎么操作:很简单,先保留前驱和后继节点, 再调整指针位置即可。 由于它是双向环状链表,只要把边界条件处理好,那么在头部或者尾部插入元素操作几乎是一样的,同样的道理,在头部或者尾部删除元素也是一样的。

template <class T, class Alloc = alloc>
class list {
    ...
  //根据迭代器删除元素
  iterator erase(iterator position) {
    //取到后一个节点
    link_type next_node = link_type(position.node->next);
    //取到前一个节点
    link_type prev_node = link_type(position.node->prev);
    //将前一个节点的next直接指向要删除的节点的下一个
    prev_node->next = next_node;
    //将后一个节点的prev直接指向要删除的节点的前一个
    next_node->prev = prev_node;
    //销毁节点
    destroy_node(position.node);
    //返回指向下一个node的迭代器
    return iterator(next_node);
  }

  iterator erase(iterator first, iterator last) {
      while (first != last) erase(first++);
      return last;
    }
}

list 内部提供一种所谓的迁移操作(transfer):将某连续范围的元素迁移到某个特定位置之前,技术上实现其实不难,就是节点之间的指针移动,只要明白了这个函数的原理,后面的 splice,sort,merge 函数也就一一知晓了,我们来看一下 transfer 的源码:

template <class T, class Alloc = alloc>
class list {
    ...

protected:
  //迁移操作 将[first,last)这个区间的元素移到position
  void transfer(iterator position, iterator first, iterator last) {
    if (position != last) {
      (*(link_type((*last.node).prev))).next = position.node;
      (*(link_type((*first.node).prev))).next = last.node;
      (*(link_type((*position.node).prev))).next = first.node;  
      link_type tmp = link_type((*position.node).prev);
      (*position.node).prev = (*last.node).prev;
      (*last.node).prev = (*first.node).prev; 
      (*first.node).prev = tmp;
    }
  }

}

这里改写一下好理解一点

 我们把456移到2的位置:

postion为2

first为4

last为7

  void transfer(iterator position, iterator first, iterator last) {
    if (position != last) {
        link_type tmp = link_type((*last.node).prev);
        //将4,5,6从list移除
        (*(link_type((*first.node).prev))).next = last.node;
        (*last.node).prev = (*first.node).prev; 
        //将4接到1的后面
        (*link_type((*position.node).prev)).next = first.node;
        (*first.node).prev = (*position.node).prev;
        //将将2接到6的后面
        (*position.node).prev = tmp;
        (*tmp).next = position.node;
    }
  }

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;

  //提供给traits是用
  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;

  // 定义节点指针
  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 */

  // ++和--是直接操作的指针指向next还是prev, 因为list是一个双向链表
  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;
  }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值