
怎么用 C++ 手写一个能跑的单向链表
不用 std::list,自己实现一个基础但可用的单向链表,核心就三件事:定义节点、维护头指针、避免野指针。重点不是“多优雅”,而是“增删时不崩、查时不越界、析构不漏内存”。
常见错误现象:Segmentation fault(访问已释放节点)、nullptr 解引用、插入后链断裂、删除时漏掉更新 next 指针。
- 节点结构必须含
int val和Node* next,构造函数初始化next为nullptr - 所有操作前先判空:
if (head == nullptr),别依赖“用户不会传空” - 插入/删除后立刻检查
head是否变化(比如删第一个节点,head必须重赋值) - 析构函数里必须递归或循环释放每个节点,否则内存泄漏
示例(插入头部):
void push_front(int val) {
Node* newNode = new Node(val);
newNode->next = head;
head = newNode; // 关键:head 必须更新
}
为什么 delete 后还要置 nullptr
不是语法要求,是防二次释放和悬空解引用。C++ 的 delete 不会自动把指针变空,它只归还内存——原指针仍存着旧地址,下次再 delete 或 ->next 就崩。
使用场景:所有显式 delete 节点的地方,尤其是删除中间节点时,前驱节点的 next 字段必须同步更新。
立即学习“C++免费学习笔记(深入)”;
- 删头节点:
Node* tmp = head; head = head->next; delete tmp; tmp = nullptr; - 删中间节点:
prev->next = curr->next; delete curr; curr = nullptr; - 不置
nullptr的代价:调试时极难定位,崩溃位置常在别处(比如下一次push_front)
find() 返回指针还是索引?怎么处理找不到的情况
返回 Node* 更直接,调用方能立刻用它做后续操作(比如改值、删节点)。但必须明确定义“找不到时返回 nullptr”,且调用方必须检查。
性能影响:返回索引需额外计数,无必要;返回指针零开销,但要求使用者懂所有权边界(别拿它去 delete,除非明确是删除操作)。
- 不要返回
int表示位置(-1 表示失败),链表没有 O(1) 索引访问 - 函数签名统一用
Node* find(int target),内部遍历用while (curr != nullptr) - 容易踩的坑:调用方直接
found->val = 100而不先判if (found)
为什么不用 std::unique_ptr 管理节点
可以,但手写链表教学场景下反而增加理解负担。裸指针 + 显式 new/delete 更暴露内存管理本质,也更贴近面试或嵌入式等受限环境需求。
兼容性影响:用智能指针后,Node 结构要加 std::unique_ptr<node> next</node>,所有 ->next = ... 变成 ->next = std::make_unique<node>(...)</node>,遍历逻辑从 curr = curr->next 变成 curr = curr->next.get()。
- 新手易错:忘记
.get()直接赋值,编译报错;或误用std::move导致提前释放 - 真实项目中,如果确定用 RAII,优先选
std::list,而不是手写带智能指针的链表 - 手写目的若为练基本功,裸指针的“痛感”恰恰是关键训练点
链表最难的不是写代码,是每次修改 next 指针时,脑子里得同时画出前驱、当前、后继三个节点的指向关系——少画一环,bug 就藏在那儿,等你忘掉检查 head 的时候突然爆发。










