1.1. list的介绍 list官方文档
1.2. list的常见接口 list中接口比较多,我们学习常见的接口并且了解它的底层原理即可:

在开始讲解list的常见接口之前,我们先来了解一下list中的迭代器:list中的指针是一个自定义类型的指针,该指针指向list中的某一个节点。
这里补充一个知识,迭代器的实现有两种方式:
list迭代器:
我们首先实现一个简单的list的iterator:
函数声明 | 接口说明 |
|---|---|
begin+ end | 返回第一个元素的迭代器 + 返回最后一个元素下一个位置的迭代器 |
rbegin+ rend | 返回第一个元素的 reverse_iterator, 即 end 位置 , 返回最后一个元素下一个位置的 reverse_iterator, 即 begin 位置 |
注意:

//节点类
template <class T>
struct ListNode
{
ListNode(const T& val = T())
:_pre(nullptr)
,_next(nullptr)
,_val(val)
{ }
ListNode<T>* _pre;
ListNode<T>* _next;
T _val;
};
//迭代器类
template <class T,class Ref,class Ptr>
struct ListIterator
{
typedef ListNode<T>* PNode;
//下面不需要用指针,因为我们要的就是迭代器本身,而并不是迭代器指针
typedef ListIterator<T, Ref, Ptr> Self;
/*typedef T& Ref;
typedef T* Ptr;*/
PNode _node;
ListIterator(PNode node)
:_node(node)
{ }
Ref operator * ()
{
return _node->_val;
}
Ptr operator -> ()
{
//返回的节点数据的地址
return &_node->_val;
}
Self& operator ++ ()
{
_node = _node->_next;
return *this;
}
Self operator ++ ()
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator -- ()
{
_node = _node->_pre;
return *this;
}
Self operator -- ()
{
Self tmp(*this);
_node = _node->_pre;
return tmp;
}
bool operator == (const Self& it)
{
return _node == it._node;
}
bool operator != (const Self& it)
{
return _node != it._node;
}
};构造函数(constructor) | 接口说明 |
|---|---|
list() | 构造空的 list |
list (size_type n, const value_type& val = value_type()) | 构造的 list 中包含 n 个值为 val 的元素 |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) | 用 [first, last) 区间中的元素构造 list |
//构造空的list list() { _head = new Node(); _head->_next = _head; _head->_pre = _head; }
//拷贝构造 list(const list<T>& it) { _head = new Node(); _head->_next = _head; _head->_pre = _head; for (auto i : it) { push_back(i); } }
//使用first、last区间构造 template <class InputIterator> list(InputIterator first, InputIterator last) { _head = new Node(); _head->_next = _head; _head->_pre = _head; while (first != last) { push_back(*first); ++first; } }
~list() { clear(); delete _head; _head = nullptr; }
函数声明 | 接口说明 |
|---|---|
empty | 检测 list 是否为空,是返回 true ,否则返回 false |
size | 返回 list 中有效节点的个数 |
函数声明 | 接口说明 |
|---|---|
push_front | 在 list 首元素前插入值为 val 的元素 |
pop_front | 删除 list 中第一个元素 |
push_back | 在 list 尾部插入值为 val 的元素 |
pop_back | 删除 list 中最后一个元素 |
insert | 在 list position 位置中插入值为 val 的元素 |
erase | 删除 list position 位置的元素 |
swap | 交换两个 list 中的元素 |
clear | 清空 list 中的有效元素 |

步骤:

iterator insert(iterator pos, const T& x)
{
Node* newnode = new Node(x);
Node* cur = pos._node;
Node* pre = cur->_pre;
//连接
pre->_next = newnode;
newnode->_pre = pre;
newnode->_next = cur;
cur->_pre = newnode;
return newnode;
}//push_front
void push_front(const T& x)
{
inserta(begin(), x);
}
//push_back
void push_back()
{
insert(end(), x);
}
步骤:

//earse
iterator earse(iterator pos)
{
assert(pos != end());
Node* cur = pos->_node;
Node* pre = cur->_pre;
Node* next = cur->_next;
//连接pre、next
pre->_next = next;
next->_pre = pre;
delete cur;
return iterator(next);
}//头删
void pop_front()
{
earse(begin());
}
//尾删
void pop_back()
{
earse(end());
}
其实list的交换非常简单,因为我们发现,list类中只有一个成员变量_head,所以我们只需要交换_head的值即可:
void Swap(list<T>& l)
{
std::swap(_head, l->_head);
}将链表的每一个节点释放掉即可,可是使用迭代器然后earse:
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}之前和大家讲过,我们可以将迭代器粗浅地先看做指针,而迭代器失效是因为是迭代器指向的空间被释放了。但是list的底层是一个包含头结点的双向链表,所以在插入元素的时候不存在扩容的操作,所以是不会导致迭代器失效的。只有在删除结点的时候才会导致迭代器失效,并且失效的只有被删除的一个结点,其他迭代器不会受到影响。
来看一下这段代码:
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
l.erase(it);
++it;
}
}会出现访问异常:

修改之后的代码:
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
l.erase(it++);
//++it;
}
}vectorlist 底 层 结 构 动态顺序表,一段连续空间 带头结点的双向循环链表 随 机 访 问支持随机访问,访问某个元素效率 O(1)不支持随机访问,访问某个元素 效率 O(N) 插 入 和 删 除 任意位置插入和删除效率低,需要搬移元素,时间复杂 度为 O(N) ,插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 任意位置插入和删除效率高,不 需要搬移元素,时间复杂度为 O(1) 空 间 利 用 率 底层为连续空间,不容易造成内存碎片,空间利用率 高,缓存利用率高 底层节点动态开辟,小节点容易 造成内存碎片,空间利用率低,缓存利用率低 迭 代 器 原生态指针 对原生态指针 ( 节点指针 ) 进行封装 迭 代 器 失 效 在插入元素时,要给所有的迭代器重新赋值,因为插入 元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 插入元素不会导致迭代器失效, 删除元素时,只会导致当前迭代 器失效,其他迭代器不受影响 使 用 场 景 需要高效存储,支持随机访问,不关心插入删除效率 大量插入和删除操作,不关心随 机访问
(本篇完)