双向链表核心是node需显式初始化next/prev为nullptr,linkedlist仅维护head/tail;插入先连新节点指针再改原链邻居;删除须判空、同步更新head/tail、暂存待删指针;遍历时删节点要先保存next;拷贝/赋值需深拷贝并防自赋值。

怎么写一个能用的 Node 和 LinkedList 类
双向链表的核心是每个节点得存前后两个指针,不是单向链表加个 prev 就完事——漏掉初始化或赋值顺序不对,后面遍历直接崩。
实操建议:
-
Node构造函数里必须显式初始化next和prev为nullptr,否则野指针一访问就Segmentation fault -
LinkedList类里只保留head和tail两个指针,别存size——除非你真需要 O(1) 获取长度,否则每次增删都要同步更新,容易错 - 插入节点时,先连新节点的
next/prev,再改原链上邻居的指针;顺序反了会断链
示例(插入到尾部):
void push_back(int val) {
Node* newNode = new Node(val);
if (!head) {
head = tail = newNode;
} else {
newNode->prev = tail; // 先连新节点的 prev
tail->next = newNode; // 再改旧 tail 的 next
tail = newNode;
}
}为什么 pop_front() 容易触发 double-free 或空指针解引用
删头节点不光是移动 head,还涉及内存释放时机和边界判断。很多练习代码在链表为空时仍调用 delete head,或者删完没把 tail 也置空。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:
- 链表只剩一个节点时
pop_front()后,head变nullptr,但tail还指着已释放内存 → 后续push_back()写tail->next崩溃 - 没检查
head == nullptr就直接head->next,运行时报bus error
实操建议:
- 所有删除操作开头加
if (!head) return; - 删最后一个节点时,必须同时置空
head和tail - 用
delete之前,把要删节点的指针先存到局部变量,删完立刻设为nullptr(比如Node* toDel = head; head = head->next; delete toDel; toDel = nullptr;)
迭代器失效问题:为什么不能边遍历边 erase()
C++ 标准库容器的 erase() 返回下一个有效迭代器,但手写双向链表如果没实现类似机制,用 for (auto p = head; p; p = p->next) 遍历时调 erase(p),p->next 已经被改或释放,下一轮循环直接 UB。
使用场景:
- 需要根据条件删多个节点(比如删所有偶数值)
- 在遍历中动态修改结构,而不是只删固定位置
实操建议:
- 不要依赖
p = p->next继续循环;删节点前先保存next:
for (Node* p = head; p; ) {
Node* next = p->next;
if (p->val % 2 == 0) erase(p);
p = next;
}- 如果封装了
erase(Node*),它内部应负责断开前后连接,但不负责内存释放——由调用方决定是否delete,避免重复释放
拷贝构造和赋值运算符为什么常被忽略,又为什么必须写
默认生成的拷贝构造只是浅拷贝指针,两个 LinkedList 对象共享同一段堆内存,一个析构后另一个再访问就是悬垂指针,use-after-free 稳定复现。
性能 / 兼容性影响:
- 不写深拷贝,传值参数、容器里存对象、临时对象返回都会出问题
- 写了但没处理自赋值(
a = a),可能把自己删空 - 用
std::unique_ptr<node></node>可以规避手动管理,但练习目的就是练裸指针逻辑,所以必须手写
实操建议:
- 拷贝构造:从
head开始逐个new节点,用临时指针维护新链的tail - 赋值运算符:先检查自赋值,再清空当前链(调用私有
clear()),再按拷贝构造逻辑重建 - 析构函数里循环
delete每个节点,别只删head
复杂点在于所有指针操作都得对称:next 和 prev 必须成对设置/断开,少一个就导致某段内存永远无法释放,或者遍历卡死在环里。











