C++ : list
💬 :如果你在阅读过程中有任何疑问或想要进一步探讨的内容,欢迎在评论区畅所欲言!我们一起学习、共同成长~!
👍 :如果你觉得这篇文章还不错,不妨顺手点个赞、加入收藏,并分享给更多的朋友噢~!
1. 基本概念
数据结构本质:std::list 是 C++ 标准库中的双向链表容器,元素存储在独立节点中,每个节点通过指针连接,内存非连续分配。
使用时包含头文件 <list> ;部分/全部展开命名空间。
核心特性:
- 作为序列式容器,支持双向迭代(可前后遍历);
- 任意位置插入 / 删除操作时间复杂度为常数级(O (1)),相比 array、vector、deque,此类操作效率更高。
与 forward_list 的区别:
forward_list 是单链表(仅支持单向迭代),结构更简单、空间更高效;list 为双向链表(支持双向迭代)。
主要缺陷:
- 不支持通过索引(operator[] 或 迭代器+/-n)随机访问,访问第 n 个元素需从头部 / 尾部逐个移动;
- 额外空间开销(每个节点需存储前驱 / 后继指针,对存储小元素的大 list 影响较明显)。
2. list 的常见重要接口
2.1 构造
list() | 默认构造:创建空链表 |
list (size_type n, const value_type& val) | 创建包含 n 个值为 val 的元素的链表 |
list (InputIterator first, InputIterator last) | 用迭代器 [first, last) 范围内的元素构造链表 |
list (const list& other) | 拷贝构造:复制 other 的所有元素 |
#include <iostream>
#include <list>
using namespace std;int main()
{// 默认构造list<int> mylist1;// 创建包含 n 个值为 val 的元素的链表list<int> mylist2(5, 10);// 用迭代器 [first, last) 范围内的元素构造链表int arr[] = { 1, 2, 3, 4, 5 };list<int> mylist3(arr, arr + 5);// 拷贝构造list<int> mylist4(mylist2);cout << "mylist1: ";for (auto it = mylist1.begin(); it != mylist1.end(); ++it)cout << *it << " ";cout << endl;cout << "mylist3: ";for (auto it = mylist3.begin(); it != mylist3.end(); ++it)cout << *it << " ";cout << endl;return 0;
}
2.2 迭代器
begin() | 返回指向第一个元素的正向迭代器 |
end() | 返回指向最后一个元素后一位置的正向迭代器 |
rbegin() | 返回指向最后一个元素的反向迭代器 |
rend() | 返回指向第一个元素前一位置的反向迭代器 |
正向遍历:
for (auto it = mylist.begin(); it != mylist.end(); ++it){cout << *it << " ";}
cout << endl;
反向遍历:
for (auto it = mylist.rbegin(); it != mylist.rend(); ++it){cout << *it << " ";}
cout << endl;
2.3 容量
empty() | 检测list是否为空,空则返回true,否则返回false |
size() | 返回list中有效节点的个数 |
2.4 元素访问
front() | 返回第一个元素的引用(未定义行为:链表为空时调用) |
back() | 返回最后一个元素的引用(未定义行为:链表为空时调用) |
cout << "第一个元素: " << mylist.front() << endl;
cout << "最后一个元素: " << mylist.back() << endl;
2.5 修改
push_front(val) | 在头部插入元素 val |
pop_front() | 删除头部元素 |
push_back(val) | 在尾部插入元素 val |
pop_back() | 删除尾部元素 |
insert(it, val) | 在迭代器 it 指向的位置处插入 val ,返回新元素的迭代器 |
erase(it) | 删除迭代器 it 指向的元素,返回下一个元素的迭代器 |
clear() | 清空链表 |
swap(other) | 交换两个链表的内容,仅交换内部指针 |
使用 insert() 或 erase() 时要定位元素,list 中可以使用 std::advance(it, n) 。
it :一个迭代器的引用,表示待移动的迭代器
n :移动的步数(整数类型)
n > 0
:后移;n < 0
:前移(仅当迭代器支持反向操作时有效,如双向迭代器)使用时要包含头文件
<iterator>
#include <iostream>
#include <list>
#include <iterator>
using namespace std;int main()
{list<int> mylist;mylist.push_front(1);mylist.push_front(2);mylist.push_front(3);cout << "使用 push_front 头插元素后:";for (int val : mylist)cout << val << " ";cout << endl;mylist.push_back(4);mylist.push_back(5);cout << "使用 push_back 尾插元素后:";for (int val : mylist)cout << val << " ";cout << endl;mylist.pop_front();cout << "使用 pop_front 删除头部元素后:";for (int val : mylist)cout << val << " ";cout << endl;mylist.pop_back();cout << "使用 pop_back 删除尾部元素后:";for (int val : mylist)cout << val << " ";cout << endl;auto it = mylist.begin();advance(it, 1); // 头部迭代器后移1位auto new_it = mylist.insert(it, 99); // 第2个位置处插入cout << "使用 insert 在指定位置插入 99 后:";for (int val : mylist)cout << val << " ";cout << endl;it = mylist.begin();advance(it, 2); // 头部迭代器后移2位auto next_it = mylist.erase(it); // 第三个位置处删除cout << "使用 erase 删除指定位置元素后:";for (int val : mylist)cout << val << " ";cout << endl;mylist.clear();cout << "使用 clear 清空链表后,链表是否为空:"<< (mylist.empty() ? "是" : "否") << endl;return 0;
}
2.6 迭代器失效问题
- 插入操作:插入元素不会使任何迭代器 / 引用失效(新节点独立分配内存)。
- 交换操作:交换两个链表的内容不会使迭代器失效(迭代器仍指向原链表的元素,但原链表已与另一个链表交换内容)。
- 删除操作:仅使指向被删除元素的迭代器 / 引用失效,其他迭代器不受影响。
易错点1:循环中直接删除元素后继续使用原迭代器
for (auto it = list.begin(); it != list.end(); ++it)
{if (*it == target) {list.erase(it); // 删除后it已失效,继续++it会导致崩溃}
}
改正:用 erase
的返回值更新迭代器
for (auto it = list.begin(); it != list.end(); )
{if (*it == target) {it = list.erase(it); // 返回下一个有效迭代器} else {++it; // 不删除时递增}
}
易错点2:删除元素后访问已失效的迭代器
auto it = list.begin();
list.erase(it);
std::cout << *it; // 访问已失效的迭代器
改正:删除后立即通过 erase
返回值获取新迭代器
auto it = list.begin();
it = list.erase(it); // it 现在指向下一个元素或 end()
3. list 的模拟实现
3.1 反向迭代器
template<class Iterator>
class ReverseListIterator
{
public:// 明确告诉编译器 Ref/Ptr 是 Iterator 的嵌套类型(而非静态成员)typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;public:// 构造函数:用原迭代器初始化反向迭代器ReverseListIterator(Iterator it) : _it(it) {}// 解引用操作:返回原迭代器前一个位置的元素Ref operator*() {Iterator temp(_it); // 创建临时迭代器副本--temp; // 临时迭代器左移(对应反向迭代器的“当前位置”)return *temp; // 返回临时迭代器指向的元素}// 箭头操作符:通过解引用获取元素地址Ptr operator->() {return &(operator*()); // 依赖 operator*() 返回左值引用}// 前置递增:反向迭代器右移(原迭代器左移)Self& operator++() {--_it; // 原迭代器左移,对应反向迭代器右移return *this;}// 后置递增:返回旧值后递增Self operator++(int) {Self temp(*this); // 保存旧状态--_it; // 原迭代器左移return temp; // 返回旧状态}// 前置递减:反向迭代器左移(原迭代器右移)Self& operator--() {++_it; // 原迭代器右移,对应反向迭代器左移return *this;}// 后置递减:返回旧值后递减Self operator--(int) {Self temp(*this); // 保存旧状态++_it; // 原迭代器右移return temp; // 返回旧状态}// 不等比较:基于原迭代器的不等关系bool operator!=(const Self& other) const {return _it != other._it; // 直接比较原迭代器}// 相等比较:修正原代码的逻辑错误bool operator==(const Self& other) const {return _it == other._it; }private:Iterator _it; // 反向迭代器的核心:持有原迭代器
};
面/笔试高频考点 | 具体内容 |
---|---|
typename 的作用 | 明确Iterator::Ref/Ptr 是迭代器的嵌套类型(非静态成员),解决模板依赖类型歧义问题 |
与原迭代器的操作映射 | 反向迭代器
|
操作符重载细节 | 前置
|
相等 / 不等比较逻辑 | operator== 需直接比较原迭代器,与operator!= 互斥 |
3.2 双向链表节点与迭代器实现
template <typename T>
struct ListNode {T data;ListNode* prev;ListNode* next;explicit ListNode(const T& val = T()) : data(val), prev(nullptr), next(nullptr) {}
};template <typename T>
class ListIterator {
public:using iterator_category = std::bidirectional_iterator_tag;using value_type = T;using pointer = T*;using reference = T&;ListIterator(ListNode<T>* node) : m_node(node) {}reference operator*() const { return m_node->data; }pointer operator->() const { return &m_node->data; }// 前置++ListIterator& operator++() {m_node = m_node->next;return *this;}// 后置++ListIterator operator++(int) {ListIterator tmp = *this;m_node = m_node->next;return tmp;}// 前置--ListIterator& operator--() {m_node = m_node->prev;return *this;}// 后置--ListIterator operator--(int) {ListIterator tmp = *this;m_node = m_node->prev;return tmp;}bool operator==(const ListIterator& other) const { return m_node == other.m_node; }bool operator!=(const ListIterator& other) const { return m_node != other.m_node; }private:ListNode<T>* m_node;
};
3.3 链表核心类实现
template <typename T>
class MyList {
public:// 类型别名(面试常考)using value_type = T;using iterator = ListIterator<T>;using const_iterator = ListIterator<const T>;// 默认构造:初始化哨兵头节点MyList() {m_head = new ListNode<T>();m_head->prev = m_head;m_head->next = m_head;}// 拷贝构造(深拷贝)MyList(const MyList& other) {m_head = new ListNode<T>();m_head->prev = m_head;m_head->next = m_head;for (auto it = other.begin(); it != other.end(); ++it) {push_back(*it);}}// 赋值运算符(深拷贝 + 自赋值安全)MyList& operator=(const MyList& other) {if (this != &other) {clear();for (auto it = other.begin(); it != other.end(); ++it) {push_back(*it);}}return *this;}// 析构函数(释放所有节点)~MyList() {clear();delete m_head;}// 迭代器接口iterator begin() { return iterator(m_head->next); }iterator end() { return iterator(m_head); }// 核心操作:头插 & 尾插void push_front(const T& val) {insert(begin(), val);}void push_back(const T& val) {insert(end(), val);}// 核心操作:头删 & 尾删void pop_front() {erase(begin());}void pop_back() {erase(--end()); // 尾迭代器需先递减}// 插入操作(面试高频)iterator insert(iterator pos, const T& val) {ListNode<T>* curr = pos.m_node;ListNode<T>* newNode = new ListNode<T>(val);// 调整指针顺序:prev -> newNode -> currnewNode->prev = curr->prev;newNode->next = curr;curr->prev->next = newNode;curr->prev = newNode;return iterator(newNode); // 返回新节点迭代器}// 删除操作(面试高频)iterator erase(iterator pos) {if (pos == end()) return pos; // 不能删除end()ListNode<T>* delNode = pos.m_node;ListNode<T>* nextNode = delNode->next;// 断开指针连接delNode->prev->next = nextNode;nextNode->prev = delNode->prev;delete delNode; // 释放内存return iterator(nextNode); // 返回下一个元素迭代器}// 清空链表void clear() {auto curr = m_head->next;while (curr != m_head) {auto next = curr->next;delete curr;curr = next;}// 恢复哨兵头节点自环m_head->prev = m_head;m_head->next = m_head;}// 辅助功能bool empty() const { return m_head->next == m_head; }size_t size() const {size_t cnt = 0;for (auto it = begin(); it != end(); ++it) cnt++;return cnt;}T& front() { return *begin(); }T& back() { return *(--end()); }private:ListNode<T>* m_head; // 哨兵头节点(不存储数据)
};
4. 对比 list 与 vector
对比维度 | vector | list |
---|---|---|
底层结构 | 动态顺序表,一段连续内存空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,时间复杂度 O (1) | 不支持随机访问,时间复杂度 O (N) |
插入和删除 | 任意位置插入 / 删除效率低(需搬移元素,时间复杂度 O (N)); 可能触发增容(开辟新空间→拷贝→释放旧空间) | 任意位置插入 / 删除效率高(无需搬移元素,时间复杂度 O (1)) |
空间利用率 | 连续内存,不易产生内存碎片; 缓存利用率高 | 节点动态开辟,小节点易产生内存碎片; 缓存利用率低 |
迭代器 | 原生态指针(如 T*) | 对节点指针(如 ListNode*)的封装类 |
迭代器失效 | 插入可能因扩容导致所有迭代器失效; 删除需重新赋值当前迭代器否则失效 | 插入不会导致迭代器失效; 删除仅当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储、支持随机访问,不关心插入 / 删除效率的场景 | 大量插入 / 删除操作,不关心随机访问效率的场景 |