好的,这是一个关于C++ STL list 容器模拟实现的逐步讲解。list 通常基于双向循环链表 实现,提供了高效的插入和删除操作(时间复杂度为 O(1)),但随机访问效率较低(时间复杂度为 O(n))。下面我们将从节点结构开始,逐步实现一个简化的 list 模板类。
1. 节点结构 (ListNode)
链表的基本单位是节点。对于双向链表,每个节点需要包含三个部分:
_data: 存储实际数据。_prev: 指向前一个节点的指针。_next: 指向后一个节点的指针。
cpp
template<class T>
struct ListNode {
T _data;
ListNode<T>* _prev;
ListNode<T>* _next;
// 构造函数,方便节点初始化
ListNode(const T& data = T(), ListNode<T>* prev = nullptr, ListNode<T>* next = nullptr)
: _data(data)
, _prev(prev)
, _next(next)
{}
};
2. List 类框架 (MyList)
list 类需要管理整个链表。关键成员包括:
_head: 指向哨兵节点(头节点)的指针。这个节点不存储有效数据,其_prev指向链表的最后一个节点,其_next指向链表的第一个节点。这样形成一个循环结构。_size: 记录链表中有效元素的数量(可选,但实现size()时 O(1) 复杂度需要它)。
cpp
template<class T>
class MyList {
public:
// 类型定义 (简化版)
typedef ListNode<T> Node;
// 后续会添加迭代器类型定义
// 构造函数
MyList() {
// 创建头节点 (哨兵节点)
_head = new Node();
_head->_prev = _head; // 指向自己
_head->_next = _head; // 指向自己
_size = 0;
}
// 析构函数 - 需要释放所有节点
~MyList() {
clear(); // 清空所有有效节点
delete _head; // 释放头节点
_head = nullptr;
}
// 其他成员函数 (push_back, pop_back, insert, erase, begin, end, size, empty 等)
// ...
private:
Node* _head; // 指向哨兵节点
size_t _size; // 记录元素个数
};
3. 迭代器设计 (iterator)
STL 容器的精髓在于迭代器。对于链表,迭代器需要封装一个指向节点的指针,并重载相应的操作符使其行为像指针。
cpp
template<class T>
class MyList {
public:
// ... 之前的代码 ...
// 迭代器类 (嵌套在 MyList 内)
class iterator {
public:
iterator(Node* node = nullptr)
: _node(node)
{}
// 解引用操作符 - 获取数据引用
T& operator*() {
return _node->_data;
}
// 前置++
iterator& operator++() {
_node = _node->_next;
return *this;
}
// 后置++
iterator operator++(int) {
iterator tmp = *this;
_node = _node->_next;
return tmp;
}
// 前置--
iterator& operator--() {
_node = _node->_prev;
return *this;
}
// 后置--
iterator operator--(int) {
iterator tmp = *this;
_node = _node->_prev;
return tmp;
}
// 比较操作符 (通常还需要实现 !=)
bool operator==(const iterator& it) const {
return _node == it._node;
}
bool operator!=(const iterator& it) const {
return _node != it._node;
}
private:
Node* _node; // 封装当前指向的节点
friend class MyList<T>; // 允许 MyList 访问 _node
};
// 获取指向第一个有效元素的迭代器
iterator begin() {
return iterator(_head->_next); // 头节点的 next 是第一个元素
}
// 获取尾后迭代器 (最后一个元素的下一个位置)
iterator end() {
return iterator(_head); // 头节点本身是尾后位置
}
// ... 后续添加 const_iterator ...
};
4. 基本操作实现
利用节点指针和迭代器,可以实现各种操作:
-
push_back(尾插):cppvoid push_back(const T& value) { Node* tail = _head->_prev; // 当前最后一个节点 Node* newnode = new Node(value, tail, _head); // 新节点: prev=tail, next=_head tail->_next = newnode; // 原尾节点指向新节点 _head->_prev = newnode; // 头节点指向新尾节点 ++_size; } -
pop_back(尾删):cppvoid pop_back() { if (_size == 0) return; // 空链表不操作 Node* tail = _head->_prev; Node* newtail = tail->_prev; newtail->_next = _head; _head->_prev = newtail; delete tail; --_size; } -
insert(在迭代器位置前插入):cppiterator insert(iterator pos, const T& value) { Node* cur = pos._node; // pos 指向的节点 Node* prev = cur->_prev; Node* newnode = new Node(value, prev, cur); // 新节点: prev=prev, next=cur prev->_next = newnode; cur->_prev = newnode; ++_size; return iterator(newnode); // 返回指向新节点的迭代器 } -
erase(删除迭代器指向的元素):cppiterator erase(iterator pos) { if (pos == end()) return pos; // 不能删除 end() Node* cur = pos._node; Node* prev = cur->_prev; Node* next = cur->_next; prev->_next = next; next->_prev = prev; delete cur; --_size; return iterator(next); // 返回被删除元素的下一个元素的迭代器 } -
clear(清空链表):cppvoid clear() { iterator it = begin(); while (it != end()) { it = erase(it); // 利用 erase 逐个删除 } _size = 0; } -
size和empty:cppsize_t size() const { return _size; } bool empty() const { return _size == 0; }
5. 使用示例
cpp
int main() {
MyList<int> lst;
lst.push_back(1);
lst.push_back(2);
lst.push_back(3);
// 使用迭代器遍历
MyList<int>::iterator it = lst.begin();
while (it != lst.end()) {
std::cout << *it << " ";
++it;
}
std::cout << std::endl; // 输出: 1 2 3
// 在第二个元素前插入
it = lst.begin();
++it; // 指向第二个元素 (2)
lst.insert(it, 99);
// 删除第一个元素
lst.erase(lst.begin());
// 再次遍历
for (auto num : lst) { // 基于迭代器的范围 for (需 begin/end 支持)
std::cout << num << " ";
}
std::cout << std::endl; // 输出: 99 2 3
return 0;
}
总结
这个实现展示了 STL list 的核心思想:
- 双向循环链表结构:通过带哨兵头节点的双向链表实现,确保首尾操作高效。
- 迭代器封装 :将节点指针封装成迭代器类,重载操作符使其提供类似指针的接口,支持
++,--,*等操作。 - 模板化 :支持存储任意数据类型
T。 - 基本操作 :实现了
push_back,pop_back,insert,erase,begin,end,size,empty等基本接口。
实际 STL 库的 std::list 实现更为复杂,考虑了内存分配器、异常安全、const 迭代器、反向迭代器、splice 操作等。但以上代码提供了理解其底层机制和设计思路的良好基础。