空指针与虚拟头节点

一句话 虚拟头 dummy 是真实存在的节点,有地址,所以可以通过 dummy->next 修改它指向的节点;而 nullptr 只是一个"值",可以用它的值,但不能解引用——读不行,写也不行。

  • 反转链表里 pre = nullptr 没问题,为什么两两交换里必须 new 一个 dummy?
  • 结论:区别在于有没有操作 pre->next(解引用)。

空指针的值可以用,空指针本身不能被解引用:

代码合法吗解释
ListNode* pre = nullptr; cur->next = pre;✅只把 nullptr 的值赋给 cur->next,操作的是 cur
pre->next = cur;❌ 崩溃解引用空指针并修改
int v = pre->val;❌ 崩溃解引用空指针,读取也不行
ListNode* dummy = new ListNode(0); dummy->next = b;✅dummy 是真实节点,有地址,可以修改它的 next

不要记成"可以调用但不能修改" 更准确的说法:可以用空指针的值,但不能解引用空指针。解引用不管是读还是写都会崩。cur->next = pre 之所以安全,是因为这里操作的是 cur->next(cur 是真实节点),pre 只是被当作一个值。

场景为什么
反转链表:pre = nullptr 够用全程只把 pre 的值赋给 cur->next,没有 pre->next 这种操作
两两交换 / 删除节点:必须 new dummy需要写 prev->next = b,必须通过真实节点修改
1
2
3
ListNode* dummyHead = new ListNode(0);  // 虚拟头:头节点前面的哨兵
dummyHead->next = head;                 // 哨兵先挂住真实头
ListNode* cur = dummyHead;              // cur 是真正移动的工作指针

三个角色分工:

角色干什么位置变化
dummyHead哨兵,站在头节点前面永远不动
cur干活(遍历、删除、交换)一直往前走
dummyHead->next返回用:当前的真实头节点头被删/被换时自动跟着变

关键理解:

  • 删除、交换过程中真实头可能会变,但不需要单独维护"头指针"——dummy 没动过,dummyHead->next 永远指向当前的真实头。
  • 返回时用 dummyHead->next,不能用 cur:cur 走到最后已经在链表末尾(甚至 nullptr)了。
  • 哨兵不参与数据,所以它的 val 随便给(0);它的存在只是为了给头节点提供一个"前一个节点"。
  • 一句话:dummy 站岗,cur 干活,dummy->next 交卷。

链表连接靠的是 next 指针(存地址),但节点本身可以放在栈上,不用 new:

1
2
3
4
5
6
7
ListNode dummy(0, head);   // 栈上的虚拟头,不是指针
ListNode* prev = &dummy;   // 取地址当指针用
while (prev->next != nullptr && prev->next->next != nullptr) {
    swapTwo(prev);
    prev = prev->next->next;
}
return dummy.next;         // 注意是 dummy.next,不是 dummy->next
  • 题解常写 new ListNode(0) 只是图省事(LeetCode 不检查内存泄漏),本地代码用栈对象更安全,不用手动 delete。
  • 注意:不能把局部对象的地址返回出去(函数结束就销毁)——但 dummy 不会被返回,返回的是 dummy.next,安全。
  • 反转链表反而不能用真实节点当 pre:最后链表尾巴会指向这个假节点,判题器释放时会崩。这再次印证:只读场景用 nullptr,要改 next 才用真实节点。
  • dummy = 真实节点 = 有一扇可以打开的门(可以改它的 next)。
  • nullptr = 值 = 可以借给别人当目标,但它自己没有任何房间,不能开门(不能解引用)。
  • 能说出 cur->next = pre(pre 为空)为什么合法
  • 能说出 pre->next = cur(pre 为空)为什么崩溃
  • 能判断一道新题:是"读前一个节点的值"还是"改前一个节点的 next",从而决定用 nullptr 还是 dummy
  • 能用栈对象写 dummy 版本(ListNode dummy(0); ListNode* prev = &dummy;)
  • 能说出返回时为什么用 dummyHead->next 而不是 cur

相关内容