该用container/list而非手写链表时:需频繁任意位置o(1)插入/删除、实现lru缓存、双端队列、栈或需双向遍历;它开箱即用、方法均为o(1),避免手写链表的nil检查、指针同步等边界问题。

别自己写链表,除非你明确知道为什么不能用 container/list 或切片。
什么时候该用 container/list 而不是手写 Node?
绝大多数工程场景下,container/list 就是正确答案——它开箱即用、线程安全(配合锁)、支持双向操作,且所有方法都是 O(1)。你不需要处理 nil 检查、指针赋值顺序、头尾更新同步这些边界问题。
常见误用场景:
- 想存
int就直接l.PushBack(42),别纠结类型擦除——99% 的业务代码里这点开销可忽略 - 需要遍历时删节点?别用
for e := l.Front(); e != nil; e = e.Next(),改用for e := l.Front(); e != nil; { next := e.Next(); l.Remove(e); e = next } - 要按索引访问?说明你本就不该用链表——改用
[]T或带索引缓存的 wrapper
手写单向链表时,Next 字段为什么必须是 *Node?
因为 Go 的 struct 是值类型:type Node struct { Data int; Next Node } 会导致编译失败——Node 包含自身,大小无法确定。只有指针 *Node 是固定大小(通常 8 字节),结构体才能被实例化。
泛型写法更安全:
type Node[T any] struct {
Value T
Next *Node[T]
}
但要注意:
- 空链表传参是
nil,不是&Node{};头插后必须显式更新head变量 - 尾插必须检查
curr.Next == nil才能赋值,漏掉会 panic - 遍历时修改节点(比如删当前节点)会破坏后续迭代逻辑,得先备份
next := curr.Next
循环链表遍历怎么避免死循环?
终止条件不是 curr.Next != head,而是「是否回到起点」。错误写法:for curr := head; curr.Next != head; curr = curr.Next ——当 head == nil 或只有一个节点时直接崩。
正确模式:
if head == nil {
return
}
curr := head
for {
// 处理 curr
if curr.Next == head {
break
}
curr = curr.Next
}
关键点:
- 必须先判
head == nil,否则curr.Nextpanic - 单节点链表中
curr.Next == head成立,循环只执行一次 - 插入/删除时,必须同时更新前驱的
Next和新节点的Next,漏一个就断环
为什么说 Append 和 Delete 是最容易出错的操作?
不是语法难,是边界太隐蔽:
-
Append:空链表时没设head;非空时遍历到curr.Next == nil后忘了curr.Next = newNode -
Delete:删头节点后没更新head = head.Next;删中间节点时只改了prev.Next,忘了curr.Next还指向原后继,导致内存泄漏或误遍历 - 所有操作都要考虑
head == nil、head.Next == nil、head是目标节点这三种情况
调试时 panic 很少直接指向出错行,往往在下游调用才暴露——比如遍历突然中断,其实是之前某次 Next 赋值漏了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











