不用std::list而手写链表是为了精确控制内存布局、避免分配器开销,或适配嵌入式/内核等禁用stl的场景;核心约束包括预分配内存池、c接口结构体布局兼容;常见错误是未初始化指针或释放后解引用。

为什么不用 std::list 而要手写链表?
因为需要精确控制内存布局、避免分配器开销,或在嵌入式/内核场景下禁用 STL。手写链表不是为了“练基础”,而是为满足特定约束:比如节点必须落在某段预分配内存池中,或需和 C 接口共用结构体布局。
常见错误现象:Segmentation fault 频发、valgrind 报告非法读写——多数源于指针未初始化或释放后仍解引用。
-
next和prev指针声明后必须显式赋值为nullptr,哪怕后续立刻赋新值 - 若节点结构体含非 trivial 构造函数(如
std::string),就不能用malloc分配,得用new或 placement-new - 删除节点时,先保存
next指针再delete,否则访问已释放内存
Node* 和 Node** 在插入时怎么选?
单向链表头插用 Node* 就够;但要在任意位置插入并修改前驱的 next 字段,就得传 Node**——否则无法把新节点地址写回原指针变量。
使用场景:实现 insert_after(Node* pos, int val) 时,pos->next 必须被修改;若只传 Node*,函数内改的是副本,外部链表结构不变。
立即学习“C++免费学习笔记(深入)”;
- 传
Node*:适用于只读遍历、查找、或头插(直接改头指针变量本身) - 传
Node**:适用于需修改链表拓扑的操作,如插入、删除、反转 - 性能影响:多一级间接寻址,但现代 CPU 缓存友好,实际差异可忽略
void insert_after(Node** pos, int val) {
Node* new_node = new Node{val, (*pos)->next};
(*pos)->next = new_node;
}
析构函数里递归删节点会栈溢出吗?
会。尤其链表长于几千节点时,递归调用深度超过默认栈空间(Linux 一般 8MB,但每帧约 1KB,撑不过万级调用)。
正确做法是迭代删除,且必须手动将指针置 nullptr——不是为了安全,而是防止多次析构时重复释放。
- 禁止写
delete head; ~Node() { delete next; } - 析构函数只负责清理自身成员,不递归;链表类的析构函数应循环
delete所有节点 - 若节点含
std::unique_ptr<node></node>,则递归析构合法,但失去对释放顺序的控制权
双向链表的 prev 指针容易漏设哪几处?
三处:头插、尾插、中间插入。漏设 prev 不会立即崩溃,但后续反向遍历时跳过节点或进入野指针。
典型错误:new_node->prev = nullptr; 写了,但忘了 old_head->prev = new_node;;或插入到 pos 后,设了 new_node->prev = pos,却没设 pos->next->prev = new_node。
- 头插:新节点
prev = nullptr,原头节点prev = new_node - 尾插:新节点
prev = tail,原尾节点next = new_node - 中间插入:前后两个邻接指针都要更新,缺一不可
链表最难的不是写对逻辑,是确保每个指针在所有路径下都被显式设置——包括构造、插入、删除、移动语义的 operator= 和移动构造函数。漏掉任意一条边,调试时症状就飘忽不定。









