c++如何实现单向循环链表_c++链表结构进阶教程【实战】

3次阅读

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

c++如何实现单向循环链表_c++链表结构进阶教程【实战】

单向循环链表的头尾判断怎么写才不掉坑

单向循环链表没有 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_nodenew_node->next = head
  • 中间插入(已知前驱节点 prev):只需两步:new_node->next = prev->nextprev->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

text=ZqhQzanResources