手写链表重在理解指针操作、内存管理与边界处理,而非仅实现功能;需显式初始化指针、同步更新双向指针、正确遍历与析构,以避免野指针、内存泄漏和越界访问。

为什么不用 std::list 而要手写链表
因为教学、面试或嵌入式等受限环境里,你得清楚节点怎么连、内存怎么管、边界怎么判。用 std::list 看不到指针跳转逻辑,也掩盖了 new/delete 的实际开销和泄漏风险。
手写重点不在“能跑”,而在“每行代码都经得起问”:比如为什么构造函数里要把 next 设为 nullptr,为什么删除节点前必须检查 head == nullptr。
- 真实场景多是单向链表练基础,双向链表练指针对称性
- 面试常考插入/删除的边界处理(头插、尾删、空链表)
- 不手动管理内存(比如忘了
delete节点)是最高频的崩溃原因
Node 结构体里该放什么成员
最少两个:数据域 + 指针域。C++ 里别写 struct Node { int data; Node* next; }; 就完事——漏了构造函数,next 会是野指针。
正确写法必须显式初始化:
立即学习“C++免费学习笔记(深入)”;
struct Node {
int data;
Node* next;
Node(int d) : data(d), next(nullptr) {} // 关键:next 必须初始化
};- 如果存自定义类型(如
std::string),注意拷贝成本;用std::move或指针可避免深拷贝 - 别把
Node做成模板立刻上手——先用int把逻辑跑通,再泛化 - 双向链表加
prev成员时,所有插入/删除操作必须同步更新两边指针,漏一个就断链
插入操作最容易错的三处
头插看似简单,但新手常写出 new_node->next = head; head = new_node; 却没处理 head 为空的情况——其实这行本身没问题,问题出在后续遍历时崩在 nullptr->next。
- 头插前无需判断
head == nullptr,但头删必须判,否则head->next解引用崩溃 - 按值插入(如升序链表)时,循环条件别写成
curr->next != nullptr && curr->next->data ,少一个 <code>&&就越界 - 尾插要遍历到
curr->next == nullptr再接,不是curr == nullptr——后者已经越界了
析构函数里 delete 的顺序和时机
必须从头开始逐个 delete,不能递归删(栈溢出),也不能只删头节点就结束。典型错误是:
~LinkedList() {
delete head; // 错!只删了头,后面全泄漏
}- 正确做法:用临时指针保存
next,再delete当前节点,再移动指针 - 如果链表很长(比如十万节点),析构本身可能卡顿——这不是 bug,是手写链表的固有代价
- 用
std::unique_ptr<node></node>可自动管理,但那就不是“手写”了,而是混合方案,需权衡教学目标
链表真正难的不是写出来,是每次修改操作后,你能画出内存图并确认每个指针指向合法地址。这点没人替你检查,只能自己盯住 valgrind 或 ASan 输出的 use-after-free 和 invalid read。









