📚 一、list容器介绍
1.1 基本概念
list是C++标准模板库(STL)中的一个序列容器,底层实现为带头节点的双向循环链表。这种结构使得list在任意位置插入和删除元素都具有很高的效率。
1.2 核心特性
双向访问:可以从前后两个方向遍历
动态内存:每个元素独立分配内存(节点)
迭代器类型:双向迭代器(支持++和--操作)
内存不连续:元素在内存中不是连续存储的
🔧 二、list的基本使用
2.1 构造函数
cpp
#include <list> #include <iostream> void test_construction() { // 1. 默认构造 - 空list std::list<int> list1; // 2. 构造包含n个相同元素的list std::list<int> list2(5, 100); // 5个100 // 3. 范围构造 int arr[] = {1, 2, 3, 4, 5}; std::list<int> list3(arr, arr + 5); // 4. 拷贝构造 std::list<int> list4(list3); // 5. 初始化列表构造(C++11) std::list<int> list5 = {1, 2, 3, 4, 5}; }2.2 迭代器使用
cpp
void test_iterator() { std::list<int> lst = {1, 2, 3, 4, 5}; // 正向迭代器 std::cout << "正向遍历: "; for (auto it = lst.begin(); it != lst.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; // 反向迭代器 std::cout << "反向遍历: "; for (auto rit = lst.rbegin(); rit != lst.rend(); ++rit) { std::cout << *rit << " "; } std::cout << std::endl; // C++11范围for循环 std::cout << "范围for遍历: "; for (int val : lst) { std::cout << val << " "; } std::cout << std::endl; }2.3 容量操作
cpp
void test_capacity() { std::list<int> lst = {1, 2, 3}; std::cout << "是否为空: " << lst.empty() << std::endl; std::cout << "元素个数: " << lst.size() << std::endl; // list没有capacity()方法,因为链表不需要预分配空间 }2.4 元素访问
cpp
void test_element_access() { std::list<int> lst = {1, 2, 3, 4, 5}; if (!lst.empty()) { std::cout << "第一个元素: " << lst.front() << std::endl; std::cout << "最后一个元素: " << lst.back() << std::endl; } // list不支持随机访问,所以没有operator[]和at()方法 // 要访问第n个元素,需要遍历 }2.5 修改操作
cpp
void test_modifiers() { std::list<int> lst; // 头部操作 lst.push_front(10); // 头部插入 lst.pop_front(); // 头部删除 // 尾部操作 lst.push_back(20); // 尾部插入 lst.pop_back(); // 尾部删除 // 中间插入 auto it = lst.begin(); ++it; // 移动到第二个位置 lst.insert(it, 30); // 在第二个位置插入30 // 删除 it = lst.begin(); lst.erase(it); // 删除第一个元素 // 清空 lst.clear(); }⚠️ 三、迭代器失效问题
3.1 错误示例
cpp
// 错误写法:迭代器失效 void wrong_example() { std::list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); while (it != lst.end()) { lst.erase(it); // 错误!删除后it失效 ++it; // 使用失效的迭代器,未定义行为 } }3.2 正确写法
cpp
// 正确写法1:使用返回值更新迭代器 void correct_example1() { std::list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); while (it != lst.end()) { it = lst.erase(it); // erase返回下一个有效迭代器 } } // 正确写法2:后置递增 void correct_example2() { std::list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); while (it != lst.end()) { lst.erase(it++); // 先传递it,然后递增 } }注意:对于list,只有指向被删除元素的迭代器会失效,其他迭代器不受影响。
🔨 四、list的模拟实现
4.1 节点结构
cpp
template<class T> struct ListNode { ListNode<T>* _prev; ListNode<T>* _next; T _data; ListNode(const T& val = T()) : _prev(nullptr) , _next(nullptr) , _data(val) {} };4.2 list类框架
cpp
template<class T> class List { private: ListNode<T>* _head; // 头节点(哨兵节点) public: // 迭代器类 class iterator { private: ListNode<T>* _node; public: iterator(ListNode<T>* node = nullptr) : _node(node) {} // 前置++ iterator& operator++() { _node = _node->_next; return *this; } // 后置++ iterator operator++(int) { iterator temp(*this); _node = _node->_next; return temp; } // 解引用 T& operator*() { return _node->_data; } // 箭头运算符 T* operator->() { return &_node->_data; } // 比较运算符 bool operator!=(const iterator& it) { return _node != it._node; } bool operator==(const iterator& it) { return _node == it._node; } }; // 构造函数 List() { _head = new ListNode<T>(); _head->_prev = _head; _head->_next = _head; } // 析构函数 ~List() { clear(); delete _head; _head = nullptr; } iterator begin() { return iterator(_head->_next); } iterator end() { return iterator(_head); } // 插入、删除等其他方法... };4.3 反向迭代器实现
cpp
template<class Iterator> class ReverseIterator { private: Iterator _it; public: ReverseIterator(Iterator it) : _it(it) {} // 前置++ ReverseIterator& operator++() { --_it; return *this; } // 后置++ ReverseIterator operator++(int) { ReverseIterator temp(*this); --_it; return temp; } // 解引用(注意反向迭代器的特殊处理) typename Iterator::reference operator*() { Iterator temp = _it; --temp; return *temp; } // 其他必要操作... };📊 五、list与vector对比
| 特性 | vector | list |
|---|---|---|
| 底层结构 | 动态数组,连续内存 | 双向链表,非连续内存 |
| 随机访问 | O(1)(支持) | O(n)(不支持) |
| 插入/删除 | 尾部O(1),其他位置O(n) | 任意位置O(1)(已知位置) |
| 内存使用 | 连续,缓存友好,空间利用率高 | 碎片化,缓存不友好,每个元素额外开销 |
| 迭代器类型 | 随机访问迭代器 | 双向迭代器 |
| 迭代器失效 | 插入/删除可能导致全部迭代器失效 | 只有被删除元素的迭代器失效 |
| 适用场景 | 需要随机访问,尾部操作频繁 | 频繁在任意位置插入/删除 |
🎯 六、选择指南
使用vector的场景:
需要频繁随机访问元素
元素数量变化不大,主要在尾部操作
对缓存性能要求高
需要与其他C函数或库交互(连续内存)
使用list的场景:
频繁在中间位置插入/删除元素
不需要随机访问,主要是顺序访问
元素较大,移动成本高
需要稳定的迭代器(不被插入操作影响)
💡 七、最佳实践
优先选择vector:除非有明确的理由,否则vector通常是更好的选择
考虑deque:如果需要头尾高效操作,考虑使用deque
使用算法库:结合STL算法(如sort、find等)使用
注意迭代器失效:特别是在循环中修改容器时
总结
list作为STL中的双向链表容器,在特定场景下具有不可替代的优势。理解其底层实现原理、迭代器机制以及与vector的区别,能够帮助我们在实际开发中做出更合理的选择。通过模拟实现list,我们能够更深入地理解STL容器的设计思想,提升C++编程能力。