链表题丢分最严重的不是逻辑不会,而是"空指针异常(NULL Pointer)"。
你的代码如果能在关键时刻加一句
if检查,在导师眼里就是"专业"和"稳重"的代名词。
🛡️ 链表防御性编程
核心思想:"只要后面带着 ->,就必须保证前面不是 NULL。"
1. 单链表的"二级跳"防御
场景:你需要访问当前结点的"后继的后继"(即跳过一个结点)。
-
❌ 危险写法(小白):
cppp->jump = p->next->next; // 如果 p 是最后一个,p->next 就是 NULL,程序直接崩溃! -
✅ 防御写法(408标准):
cppif (p->next != NULL) { p->jump = p->next->next; // 只有确定有"下一位",才敢去摸"下下位" } else { p->jump = NULL; // 后面没人了,直接置空 }
2. 双向链表的"四步插入"防御(核心考点)
场景 :在结点 p 之后插入结点 s。双向链表的精髓在于不仅要连 next,还要连 prior。
-
❌ 危险写法:
cpps->next = p->next; p->next->prior = s; // 报错点:如果 p 是最后一个结点,p->next 为 NULL,NULL 没有 prior! s->prior = p; p->next = s; -
✅ 防御写法(顺序不能错,判空不能省):
cpps->next = p->next; if (p->next != NULL) { // 【防御核心】判断 p 是不是尾巴 p->next->prior = s; } s->prior = p; p->next = s;
3. 链表删除的"闭环"防御
场景 :删除 p 结点的后继结点 q。
-
✅ 防御写法:
cppq = p->next; if (q == NULL) return false; // 防御:后面没东西,删个寂寞 p->next = q->next; if (q->next != NULL) // 【防御核心】如果要删的不是最后一个 { q->next->prior = p; } free(q);
📊 防御性编程对照表(烂熟于心)
| 操作场景 | 风险代码 | 潜在后果 | 防御措施 |
|---|---|---|---|
| 访问后继 | p = p->next; |
p 变 NULL 后继续操作 |
检查 p != NULL |
| 访问后继的后继 | p->next->next |
空指针崩溃 | 检查 p->next != NULL |
| 修改前驱 | q->next->prior = p |
空指针崩溃 | 检查 q->next != NULL |
| 头插/尾插 | 直接修改 L->next |
丢失原链表 | 引入 r 指针或备份 L->next |
📝 2026 年真题 Q2 逻辑复刻
真题里那个 D 选项为什么是对的?因为它完美执行了刚说的逻辑:

题目背景: 双向链表 [p2, data, p1],p1 是 next,p2 是我们要修改的指针。要把 p2 指向 p1->p1(即后继的后继)。
标准执行逻辑:
cpp
while (cu != NULL) { // 只要还没走到头
if (cu->p1 != NULL) { // 防御:如果有下一个结点
cu->p2 = cu->p1->p1; // 让 p2 指向"下下个"(哪怕下下个是 NULL 也没关系)
} else {
cu->p2 = NULL; // 如果没有下一个,那更不可能有下下个
}
cu = cu->p1; // 指针正常后移
}
这是一段双向链表删除节点 的防御性实现,目标是删除链表中节点 p 的直接后继节点 q。
在 C/C++ 这类手动管理内存的语言中:
-
链表节点通常是通过
malloc/calloc等函数动态分配在堆内存中的。 -
当
q被从链表中移除后,它不再被任何指针引用,但堆内存不会自动回收。 -
如果不调用
free(q),这块内存会一直被占用,直到程序结束,造成内存泄漏。长期运行的程序会因内存泄漏逐渐耗尽系统资源,导致性能下降甚至崩溃。
💡 补充 Part1的"双向链表"和"删除/插入操作" 模块
总结一句话: > 只要在代码里写 ->,你的大脑就要立刻反射出:"它前面的那个东西会不会是 NULL?"