1. list 的介绍
① STL::list 的底层实现是带头双向循环链表结构。
② list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
③ 双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
2. list 的使用
2.1 list 的构造
| 构造函数 | 接口说明 |
|---|---|
list(size_t n , const T& val = T()) | 构造的 list 中包含 n 个值为 val 的元素 |
list() | 构造空的 list |
list(const list& x) | 拷贝构造函数 |
list(InputIterator first ,InputIterator last) | 用 [first, last) 区间中的元素构造 list |
int main() {
list<int> lt1; // 构造空的 lt1
list<int> lt2(4, 100); // lt2 中放 4 个值为 100 的元素
list<int> lt3(lt2.begin(), lt2.end()); // 用 lt2 的 [begin(), end())左闭右开的区间构造 lt3
list<int> lt4(lt3); // 用 lt3 拷贝构造 lt4
return 0;
}
2.2 list iterator 的使用
注意:list 的迭代器的底层是链表的节点,其访问链表中的数据,是通过操作符重载的方式来进行实现的。
| 函数声明 | 接口说明 |
|---|---|
begin + end | 返回第一个元素的迭代器 + 返回最后一个元素的下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的 reverse_iterator 即 end 位置 + 返回最后一个元素的下一个位置的 reverse_iterator,即 begin 位置 |

2.3 list 关于容量的函数
| 函数声明 | 接口说明 |
|---|---|
empty() | 检测 list 是否为空,是返回 true,不是则返回 false。 |
size() | 返回 list 有效节点的个数。 |
#include<iostream>
#include<list>
using namespace std;
int main() {
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
cout << "size:" << lt.size() << endl;
cout << "empty:" << lt.empty() << endl;
return 0;
}
2.4 list 中的数据访问函数
| 函数声明 | 接口说明 |
|---|---|
front | 返回 list 的第一个节点中的引用 |
back | 返回 list 的最后一个节点中的值的引用 |
2.5 list 关于数据的函数
| 函数声明 | 接口说明 |
|---|---|
push_front | 在 list 首元素前插入值为 val 的元素 |
pop_front | 删除 list 中的第一个元素 |
push_back | 在 list 尾部插入值为 val 的元素 |
pop_back | 删除 list 中最后一个元素 |
insert | 在 list 的 pos 位置中插入值为 val 的元素 |
erase | 删除 list 中 pos 位置的元素 |
swap | 交换两个 list 中的元素 |
clear | 清空 list 中的有效元素 |
// list 插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3() {
int array[] = { 1, 2, 3 };
list<int> L(array, array + sizeof(array) / sizeof(array[0]));
// 在 list 的尾部插入 4,头部插入 0
L.push_back(4);
L.push_front(0);
PrintList(L);
// 删除 list 尾部节点和头部节点
L.pop_back();
L.pop_front();
PrintList(L);
}
// insert /erase
void TestList4() {
int array1[] = { 1, 2, 3 };
list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));
// 获取链表中第二个节点
auto pos = ++L.begin();
cout << *pos << endl;
// 在 pos 前插入值为 4 的元素
L.insert(pos, 4);
PrintList(L);
// 在 pos 前插入 5 个值为 5 的元素
L.insert(pos, 5, 5);
PrintList(L);
// 在 pos 前插入 [v.begin(), v.end) 区间中的元素
vector<int> v{ 7, 8, 9 };
L.insert(pos, v.begin(), v.end());
PrintList(L);
// 删除 pos 位置上的元素
L.erase(pos);
PrintList(L);
// 删除 list 中 [begin, end) 区间中的元素,即删除 list 中的所有元素
L.erase(L.begin(), L.end());
PrintList(L);
}
// resize/swap/clear
void TestList5() {
// 用数组来构造 list
int array1[] = { 1, 2, 3 };
list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
PrintList(l1);
// 交换 l1 和 l2 中的元素
list<int> l2;
l1.swap(l2);
PrintList(l1);
PrintList(l2);
// 将 l2 中的元素清空
l2.clear();
cout << l2.size() << endl;
}
2.6 list 迭代器失效
迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list 的底层结构为带头结点的双向循环链表,因此在 list 中进行插入时是不会导致 list 的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
示例:
void TestListIterator1() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> lt(array, array+sizeof(array)/sizeof(array[0]));
auto it = lt.begin();
while (it != lt.end()) {
// erase() 函数执行后,it 所指向的节点已被删除,
//因此 it 无效,在下一次使用 it 时,必须先给其赋值
lt.erase(it);
++it;
}
}
// 改正
void TestListIterator() {
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> lt(array, array+sizeof(array)/sizeof(array[0]));
auto it = lt.begin();
while (it != lt.end()) {
lt.erase(it++); // it = l.erase(it);
}
}
3. list 中的 sort 和算法库中的区别
list 中的 sort 底层实现是归并排序实现的。(时间复杂度高,效率低)
算法库中的 sort 底层是快排实现的。(时间复杂度低,效率高)

效率测试和对比:

4. list 的模拟实现及正向和反向迭代器解析
4.1 list 的迭代器封装
list 的迭代器和 string 和 vector 迭代器的区别: 首先我们得清楚,list 中每一个节点,在空间都是非连续存储的,不像 string 和 vector 的迭代器一样直接对迭代器++/--即可,由于其底层是内置类型的指针形似而它们在内存中存储数据又是在连续的空间上存储的,不需要我们进行因此我们在对迭代器做 ++/ -- 等操作的时候重载运算符。
我们应该怎么办呢?
通过封装迭代器,对++、--等运算符实现重载。
迭代器的成员变量的解析:

4.2 迭代器模板参数的解析

4.3 正向迭代器模拟实现
template<class T> struct list_node {
list_node<T>* _next=nullptr;
list_node<T>* _prev=nullptr;
T _val;
list_node(const T& val = T()) :_val(val) {}
};
template<class T,class Ref,class Ptr> struct __list_iterator {
typedef list_node<T> Node;
typedef __list_iterator<T, Ref,Ptr> Self;
Node* _node;
__list_iterator(Node* node) :_node(node) {}
Ref operator*() { return _node->_val; }
Ptr operator->() { return &_node->_val; }
Self& operator++() { _node = _node->_next; return *this; }
Self operator++(int) { Self tmp(*this); _node = _node->_next; return tmp; }
Self& operator--() { _node = _node->_prev; return *this; }
Self operator--(int) { Self tmp(*this); _node = _node->_prev; return tmp; }
bool operator!=(const Self& it) const { return _node != it._node; }
bool operator==(const Self& it) const { return _node == it._node; }
};
4.4 反向迭代器模拟实现
原理和正向迭代器差不多,反向迭代器的成员变量的类型是正向迭代器,通过操作符重载的方式实现逻辑变化,反向迭代器的操作符重载实现,底层原理都是去调用正向迭代器的操作符重载,唯一区别就是逻辑不同。
template<class iterator,class Ref,class Ptr> struct Reverse_iterator {
public:
typedef Reverse_iterator<iterator, Ref, Ptr> Self;
iterator _it;
Reverse_iterator(iterator it) :_it(it) { }
Self operator++() { --_it; return *this; }
Self operator--() { ++_it; return *this; }
Ref operator*() { iterator tmp = _it; return *(--tmp); }
Ptr operator->() { return &(operator*()); }
bool operator!=(const Self& s) const { return _it != s._it; }
};
4.5 list 函数模拟实现
template<class T> struct list_node {
list_node<T>* _next=nullptr;
list_node<T>* _prev=nullptr;
T _val;
list_node(const T& val = T()) :_val(val) {}
};
template<class T> class list {
typedef list_node<T> Node;
public:
typedef __list_iterator<T,T&,T*> iterator;
typedef __list_iterator<T, const T&,const T*> const_iterator;
typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef Reverse_iterator<const_iterator, const T&,const T*> reverse_const_iterator;
//反向迭代器
reverse_iterator rbegin() { return reverse_iterator(_head); }
reverse_iterator rend() { return reverse_iterator(_head->_next); }
//const 反向迭代器
reverse_const_iterator rbegin() const { return reverse_const_iterator(_head); }
reverse_const_iterator rend() const { return reverse_const_iterator(_head->_next); }
//正向迭代器
iterator begin() { return iterator(_head->_next); }
iterator end() { return _head; }
//const 正向迭代器
const_iterator begin() const { return const_iterator(_head->_next); }
const_iterator end() const { return const_iterator(_head); }
void empty_init() {
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_sz = 0;
}
list() { empty_init(); }
list(const list<T>& lt) {
empty_init();
auto it = lt.begin();
while (it != lt.end()) {
push_back(*it);
++it;
}
}
void swap(list<T>& lt) {
std::swap(_head, lt._head);
std::swap(_sz, lt._sz);
}
list<T>& operator=(list<T> lt) {
swap(lt);
return *this;
}
void push_back(const T& x) { insert(end(), x); }
void push_front(const T& x) { insert(begin(), x); }
void pop_back() { erase(--end()); }
void pop_front() { erase(begin()); }
//pos 之前插入
iterator insert(iterator pos, const T& x) {
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
cur->_prev = newnode;
newnode->_prev = prev;
prev->_next = newnode;
newnode->_next = cur;
++_sz;
return newnode;
}
iterator erase(iterator pos) {
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
--_sz;
return next;
}
size_t size() { return _sz; }
void clear() {
list<T>::iterator it = begin();
while (it != end()) {
it = erase(it);
}
_sz = 0;
}
~list() { clear(); delete _head; _head = nullptr; }
private:
Node* _head;
size_t _sz; //用于记录 list 中有效数据的个数
};
5. list 和 vector 的对比
| vector | list | |
|---|---|---|
| 底层结构 | 动态顺序链表,一段连续空间 | 带头结点的双向循环链表 |
| 随机访问 | 支持随机访问,访问某个元素效率 O(1) | 不支持随机访问,访问某个元素效率 O(N) |
| 插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为 O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针 (节点指针) 进行封装 |
| 迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
| } |


