stl标准库系列之--list

1、概述

list 容器,是序列容器的一种,是一个双向链表,因此又被称作双向链表容器。相较于vector的连续性空间来说,list会显得比较复杂。即该容器的底层是以双向链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

可以看到,list 容器中各个元素的前后顺序是靠指针来维系的,每个元素都配备了 2 个指针,分别指向它的前一个元素和后一个元素。其中第一个元素的前向指针总为 null,因为它前面没有元素;同样,尾部元素的后向指针也总为 null。

基于这样的存储结构,list 容器具有一些其它容器(array、vector 和 deque)所不具备的优势,即它可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1))。并且在 list 容器中移动元素,也比其它容器的效率高。

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

2、节点 node

既然list是一个双向链表,那么我们就不得不了解下list的结点。

struct _List_node_base {
    _List_node_base* _M_next;
    _List_node_base* _M_prev;
};

template <class _Tp>
struct _List_node : public _List_node_base {
    _Tp _M_data;
};

我们最常见的版本。

template <class _T>
struct _List_node{
    typedef void* void_pointer;
    _T _data;
    void_pointer prev;	//可设置为_List_node<_T>*
    void_pointer next;
};

在这里插入图片描述

3、定义

template <class _Tp, class _Alloc>
void 
_List_base<_Tp,_Alloc>::clear() 
{
  _List_node<_Tp>* __cur = (_List_node<_Tp>*) _M_node->_M_next;
  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_next = _M_node;
  _M_node->_M_prev = _M_node;
}

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class list : protected _List_base<_Tp, _Alloc> {
  // requirements:

  __STL_CLASS_REQUIRES(_Tp, _Assignable);

  typedef _List_base<_Tp, _Alloc> _Base;
protected:
  typedef void* _Void_pointer;

public:      
  typedef _Tp value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef _List_node<_Tp> _Node;
}

上面代码是SGI 版本STL源码中list的定义,我们将上面这段代码简化一下。

template <class T, class Alloc = alloc>	//缺省使用 alloc 为配置器
class list{
protected:
	typedef _List_node<T> list_node;
public:
	typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  	typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
}

上面是简化后的版本。SGI 版本的list是个环状的双向链表。

为了方便的实现list模板类提供的函数,该模板在为了更方便的实现 list 模板类提供的函数,该模板类在构建容器时,会刻意在容器链表中添加一个空白节点,并作为 list 链表的首个节点(又称头节点)。

比如我们常常实现的创建一个没有任何元素的list容器。

public:
	list(){ empty_initialize(); }	//创建一个空的list容器
	
protected:
	void empty_initialize()
	{
		node = get_node();	//配置一个节点空间,令node指向它
		node->next = node;	//令node头尾都指向自己
		node->prev = node;
	}

在这里插入图片描述

4、特点

  • 内存不一定是连续性的
  • 不支持随机存取,只能直接存取首尾的元素,想要获取到其他的元素,只能通过遍历的方式
  • 在任何位置插入或者删除,时间复杂度都是常数级的(O(n))。因为在操作的时候,不需要移动和删除其他的元素
  • 插入和删除不会造成指向其他元素的 iterator、pointer、reference失效
  • list对异常操作的处理方式:要么成功,要么什么都不发生

5、创建方法

list以类模板list<T>的形式定义在<list>中,并且位于std命名空间中。因此,使用之前需要包含:

#include <list>
using namespace std;
  • 创建一个没有任何元素的空 list 容器:

    std::list<int> data;
    

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

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

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

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

  • 创建一个包含 n 个元素的 list 容器,并为每个元素指定初始值:

    std::list<int> data(10, 5);
    
  • 在已有 list 容器的情况下,通过拷贝该容器可以创建新的 list 容器:

    std::list<int> data1(10);
    std::list<int> data2(data1);
    

    注意:通过拷贝容器的方式新建容器,则一定要保证两个容器的元素的类型一致。

  • 通过拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 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}
    

6、内存管理

我们还是以一个比较简单的例子来看下list在增、删之后的大小情况。

#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

int main(int argc, char* argv[])
{
	list<int> data;
	cout << "list size = " << data.size() << endl;	//list size = 0 创建的是大小为0的list 
	
	for(int nIndex = 1; nIndex <= 5; ++nIndex)
	{
		data.push_back(nIndex);	
	}
	
	cout << "list size = " << data.size() << endl;	//list size = 5,大小是动态创建的,每push一个成员就新增一个成员的大小 
	
	list<int>::iterator itor = data.begin();
	for(; itor != data.end(); ++itor)
	{
		cout << *itor << " ";	// 1 2 3 4 5 
	}
	cout << endl;
	
	itor = find(data.begin(), data.end(), 3);
	
	if(itor != data.end())
	{
		data.insert(itor, 10);	
	}
	
	itor = data.begin();
	for(; itor != data.end(); ++itor)
	{
		cout << *itor << " ";	// 1 2 10 3 4 5 
	}
	cout << endl;
	
	cout << "list size = " << data.size() << endl;	//list size = 6
	
	itor = find(data.begin(), data.end(), 3);
	if(itor != data.end())
	{
		itor = data.erase(itor);	//删除是可以返回下一个元素的迭代器的 
	}
	
	cout << "list size = " << data.size() << endl;	// list size = 5
	cout << *itor <<endl;	// 4 下一个元素 
	
	return 0;
}

由上面的例子我们可以看出:

  • 空的list的大小为0;当通过push_back在尾部插入元素时,list容器的大小相应的改变,使用erase删除一个元素后,大小相应改变
  • 给list容器中插入元素,插入之后的新元素在迭代器(哨兵)的前面,这是STL对于插入操作的标准规范
  • 进行元素删除时,指向其他元素的迭代器并没有失效,使用erase函数时会返回下一个元素的迭代器

7、成员函数

函数说明
void push_front(const T & val)将 val 插入链表最前面
void pop_front()删除链表最前面的元素
void sort()将链表从小到大排序
void remove (const T & val)删除和 val 相等的元素
remove_if删除符合某种条件的元素
void unique()删除所有和前一个元素相等的元素
void merge(list & x)将链表 x 合并进来并清空 x。要求链表自身和 x 都是有序的
void splice(iterator i, list & x, iterator first, iterator last)在位置 i 前面插入链表 x 中的区间 [first, last),并在链表 x 中删除该区间。
链表自身和链表 x 可以是同一个链表,只要 i 不在 [first, last) 中即可
void transfer(iterator position, iterator first, iterator last)将[first, last)连续范围内的元素全部移到某个特定的位置(position)之前
该区间可在同一个list中

list容器的成员函数大多数和vector的功能一样,这边就不在一一赘述,有需要的可以翻翻上一篇看看。

  • list容器成员函数中增加了sort(),这是因为STL的sort()函数需要随机访问迭代器的支持。而list不支持。
  • list内部提供了一个迁移操作的函数,该函数是未公开的接口。

8、迭代器

list由于其节点不保证在存储空间的连续性。因此,不能想vector一样以普通指针做为迭代器。list的迭代器必须有能力指向list的节点。并且能够进行正常的递增、递减、取值,元素存取等操作。

STL中list容器是双向链表,因此迭代器必须具备前移、后移的能力,所以list的迭代器是双向迭代器(Bidirectional iterators).

list 的特性,增加的操作并不会影响原来迭代器失效,删除时也只是“指向被删除元素”那个迭代器失效。

struct _List_iterator_base {
  typedef size_t                     size_type;
  typedef ptrdiff_t                  difference_type;
  typedef bidirectional_iterator_tag iterator_category;

  _List_node_base* _M_node;

  _List_iterator_base(_List_node_base* __x) : _M_node(__x) {}
  _List_iterator_base() {}

  void _M_incr() { _M_node = _M_node->_M_next; }
  void _M_decr() { _M_node = _M_node->_M_prev; }

  bool operator==(const _List_iterator_base& __x) const {
    return _M_node == __x._M_node;
  }
  bool operator!=(const _List_iterator_base& __x) const {
    return _M_node != __x._M_node;
  }
};
template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator : public _List_iterator_base {
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
  typedef _List_iterator<_Tp,_Ref,_Ptr>             _Self;

  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef _List_node<_Tp> _Node;

  _List_iterator(_Node* __x) : _List_iterator_base(__x) {}
  _List_iterator() {}
  _List_iterator(const iterator& __x) : _List_iterator_base(__x._M_node) {}

  //对节点取值
  reference operator*() const { return ((_Node*) _M_node)->_M_data; }

#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  _Self& operator++() { 
    this->_M_incr();
    return *this;
  }
  _Self operator++(int) { 
    _Self __tmp = *this;
    this->_M_incr();
    return __tmp;
  }
  _Self& operator--() { 
    this->_M_decr();
    return *this;
  }
  _Self operator--(int) { 
    _Self __tmp = *this;
    this->_M_decr();
    return __tmp;
  }
};

从一张图来看看list的迭代器。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值