单向循环链表尾节点判断应为 current->next == head,而非 current == head;空链表需先判 head == nullptr;遍历宜用 do-while;插入删除须维护循环性,头/尾插需更新原尾节点的 next,删唯一节点后须置 head = nullptr。

单向循环链表的头尾判断怎么写才不掉坑
单向循环链表没有 nullptr 终止点,靠“是否回到头节点”来识别尾部——但直接用 current == head 判断尾节点是错的,因为头节点本身也满足这个条件,容易漏掉第一次迭代或误判空链表。
- 空链表时
head == nullptr,必须单独检查,否则后续任何遍历都会崩溃 - 非空时,尾节点的
next指针指向head,所以判断尾部的正确方式是:current->next == head - 遍历时推荐“do-while”结构:先处理当前节点,再移动指针,避免在空链表或单节点链表中多走一轮
插入操作:头插、尾插、中间插,指针改哪几个
单向循环链表插入的核心是维护“循环性”,不是只改一个 next 就完事。比如尾插,你以为改了原尾节点的 next 和新节点的 next 就够?漏了头节点的更新(当原链表为空时)就会出问题。
- 头插:新节点
next = head;若原链表非空,需找到原尾节点(即head->prev_in_cycle),把它指向新节点;但单向链表没有prev,所以实际要遍历一圈找尾 → 更优解是:头插后,让新节点next指向原head,再把所有next指向新节点的节点统一更新为指向新head?不,太重。正确做法是:头插后,**必须同步更新原尾节点的next指向新头** —— 所以得先遍历到尾(tail->next == head),再tail->next = new_node,最后new_node->next = head,然后head = new_node - 尾插:更简单,找到尾(
tail->next == head),然后tail->next = new_node,new_node->next = head - 中间插入(已知前驱节点
prev):只需两步:new_node->next = prev->next,prev->next = new_node—— 这里不用动头尾,安全
删除节点时最容易崩的三种情况
删节点不只是 delete ptr,关键是把前后指针接上,而循环链表里“前后”关系比双向链表更隐蔽。
- 删唯一节点(
head != nullptr && head->next == head):删完必须置head = nullptr,否则head成悬垂指针,后续任何操作都未定义行为 - 删头节点:不能只改
head,还得让原尾节点(此时是head->next的前驱)的next指向新头 —— 所以仍需先遍历找尾,再tail->next = head->next,然后delete head,最后head = head->next - 删中间或尾节点:只要知道前驱,就安全;但如果只知道待删节点地址(无前驱),就必须从
head开始遍历找前驱 —— 单向链表无法反查,这是结构代价
用 C++ 实现时,struct Node 和内存管理要注意什么
别用裸 new/delete 混着智能指针玩,C++ 循环链表天然不适合 std::shared_ptr —— 会因循环引用导致内存泄漏;std::unique_ptr 又没法表达“尾连头”的所有权转移。
立即学习“C++免费学习笔记(深入)”;
- 推荐坚持裸指针 + 明确所有权:链表类(如
class CircularList)完全拥有所有Node*,析构时逐个delete -
Node定义里不要放虚函数或继承体系,避免对象大小和对齐干扰指针算术(虽然一般不影响,但嵌入式或自定义内存池时会踩坑) - 构造
Node时建议用explicit单参构造函数,防止隐式转换意外创建节点 - 如果要用容器语义(比如支持迭代器),别手写循环逻辑,直接封装成符合
LegacyForwardIterator要求的迭代器类,内部用Node*+ 链表引用,避免每次operator++都做空检查
最麻烦的从来不是“怎么连”,而是“什么时候该停”。哪怕写熟了,每次写 while (curr != head) 前都得盯三秒:这次 curr 是从哪开始的?head 有没有可能 nullptr?











