list 容器,是序列容器的一种,是一个双向链表,因此又被称作双向链表容器。相较于vector的连续性空间来说,list会显得比较复杂。即该容器的底层是以双向链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。
可以看到,list 容器中各个元素的前后顺序是靠指针来维系的,每个元素都配备了 2 个指针,分别指向它的前一个元素和后一个元素。其中第一个元素的前向指针总为 null,因为它前面没有元素;同样,尾部元素的后向指针也总为 null。
基于这样的存储结构,list 容器具有一些其它容器(array、vector 和 deque)所不具备的优势,即它可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1))。并且在 list 容器中移动元素,也比其它容器的效率高。
使用 list 容器的缺点是,它不能像 array 和 vector 那样,通过位置直接访问元素。举个例子,如果要访问 list 容器中的第 6 个元素,它不支持容器对象名[6]这种语法格式,正确的做法是从容器中第一个元素或最后一个元素开始遍历容器,直到找到该位置。
既然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;
};
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;
}
list以类模板list
的形式定义在
中,并且位于std命名空间中。因此,使用之前需要包含:
#include
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}
我们还是以一个比较简单的例子来看下list在增、删之后的大小情况。
#include
#include
#include
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;
}
由上面的例子我们可以看出:
函数 | 说明 |
---|---|
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由于其节点不保证在存储空间的连续性。因此,不能想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;
}
};