最直白单链表实现:定义node含val和next(必初始化为nullptr),new节点后立即设next=nullptr,用指针手动串联;遍历时判curr!=nullptr;头插需node*& head,删除前保存next再delete,避免悬空指针。

怎么用 C++ 写一个能跑起来的单链表
别从模板类或智能指针起步,先用最直白的方式把节点连上、遍历出来。核心就三件事:定义节点结构、手动 new 出节点、用指针串起来。
常见错误是 next 指针没初始化,导致遍历时访问野地址;或者插入时忘了更新前驱的 next,链表直接断掉。
-
struct Node { int val; Node* next; };——next必须显式初始化为nullptr,否则值随机 - 创建头节点后,立刻设
head = nullptr;每次new Node后,马上写node->next = nullptr - 遍历必须检查
curr != nullptr,不能只写curr->next != nullptr,否则最后一个节点被跳过
示例片段:
Node* head = nullptr;
Node* n1 = new Node{1, nullptr};
Node* n2 = new Node{2, nullptr};
n1->next = n2;
head = n1;
<p>Node* curr = head;
while (curr != nullptr) {
std::cout << curr->val << " ";
curr = curr->next;
}为什么不用 std::list 而要手写
不是为了造轮子,而是为了理解内存布局和指针操作——比如调试时看到 0xcdcdcdcd 或 0xbaadf00d 这类错误码,就得靠手写链表练出条件反射。
立即学习“C++免费学习笔记(深入)”;
std::list 是双向、带哨兵节点、分配器可替换的完整实现,而手写单链表只有 next 一个指针,内存连续性差、缓存不友好,但胜在每个字节都可控。
- 面试/笔试里考的是
insert_after、delete_by_value这类逻辑,std::list的erase不暴露底层指针,没法考察指针操作细节 - 嵌入式或内核模块里常禁用 STL,
new都可能被替换成自定义池分配,这时候Node*才是真实接口 - 性能上,手写链表的
push_front是 O(1),但随机访问仍是 O(n),这点不会因为换容器改变
delete 时最容易漏掉的两个点
不是“忘了 delete”,而是删得不干净,留下悬空指针或重复释放。
- 删除节点前,必须先保存它的
next,再delete当前节点,否则curr->next就丢了 - 删完要把原指针(比如
head或prev->next)设为nullptr或新值,否则它仍指向已释放内存,后续解引用就是未定义行为 - 遍历中删除时,别在循环条件里写
curr = curr->next,应在循环体内处理指针转移
典型翻车代码:delete curr; curr = curr->next; —— curr->next 此时已不可读。
头插、尾插、按值删,参数怎么传才安全
关键看谁负责改“链表入口”——也就是 head 指针本身。如果函数可能让 head 指向新节点(比如空链表时头插),就必须传 Node*& head,而不是 Node* head。
- 头插:需修改
head,参数用Node*& head - 尾插:若已有
tail指针可维护,传Node*& tail;否则只能遍历到末尾,此时head不变,传Node* head即可 - 按值删除:要改前驱的
next,所以需要二级指针或引用,如Node** prev_next,或者干脆传入Node*& head并在内部处理头节点特例
传错参数类型,编译可能过,但运行时 head 始终不变,你会以为逻辑错了,其实是调用方式错了。
链表看着简单,但每个 ->next 和每处 delete 都得对得上号;少一个 nullptr 初始化,多一次 curr = curr->next 的位置错,就卡半天。











