链表节点必须用指针类型定义,因为struct是值类型,若Next字段为Node会引发无限递归定义、编译失败;只有*Node有固定大小(如8字节),结构体才能实例化。

为什么链表节点必须用指针类型定义
Go 语言中,结构体默认是值传递,如果 Node 字段直接嵌套自身结构体(比如 Next Node),编译器会报错:invalid recursive type Node——因为类型大小无法确定。必须用指针打破递归定义,让编译器知道它只存一个地址(固定 8 字节),而非整个结构体。
正确写法是:type Node struct { Val int; Next *Node }。这里 Next 是指向 Node 的指针,不是 Node 本身。
插入操作中什么时候该传 **Node 而不是 *Node
当你需要修改头节点本身(比如在链表头部插入新节点,并让调用方看到新头)时,必须传入 **Node。否则,函数内对形参指针的重新赋值(如 head = newNode)不会影响调用方的原始变量。
常见误操作:
立即学习“go语言免费学习笔记(深入)”;
- 写
func InsertHead(head *Node, val int)然后head = &Node{Val: val, Next: head}—— 这只是改了形参,原head不变 - 正确做法是
func InsertHead(head **Node, val int),然后*head = &Node{Val: val, Next: *head}
尾插或中间插入只需 *Node,因为只修改已有节点的 Next 字段,不改变头指针本身。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
遍历时 nil 检查漏掉会导致 panic
Go 链表遍历最常见 panic 是 panic: runtime error: invalid memory address or nil pointer dereference,通常发生在没检查 current == nil 就访问 current.Next。
安全遍历模板:
for current != nil {
// 处理 current.Val
current = current.Next // 这行前 current 必须非 nil
}
特别注意边界情况:空链表(head == nil)、单节点、删除最后一个节点后忘记置空指针。
内存泄漏风险:循环引用与 GC 友好性
Go 的 GC 能自动回收不可达对象,但如果你在链表节点里存了闭包、map 或其他持有外部引用的字段,且这些引用意外延长了节点生命周期,就可能造成“逻辑上已删除,物理上未释放”。更隐蔽的是:人为保留了某个中间节点的指针(比如缓存了 *Node),又删掉了它前后节点,那它及其后续所有节点都变成不可达——但只要那个缓存指针还活着,整段子链就一直占内存。
建议:
- 删除节点后,显式置空相关引用(如
prev.Next = nil,尤其在调试时) - 避免在节点结构体中嵌套大对象或长生命周期资源
- 用
runtime.ReadMemStats对比操作前后堆分配,验证删除是否真正生效
指针让链表灵活,也把责任交给了你——谁持有指针,谁负责它的生命周期。

















