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

为什么 Go 链表节点必须用指针类型定义
Go 里 struct 是值类型,如果链表节点用值类型嵌套(比如 Next Node),会导致无限递归定义:编译器无法确定 Node 的大小。所以必须写成 Next *Node —— 只有指针有固定长度(通常 8 字节),才能让结构体可实例化。
常见错误是写成:
type Node struct {
Val int
Next Node // ❌ 编译报错:invalid recursive type Node
}
正确写法是:
type Node struct {
Val int
Next *Node
}
这也意味着:所有链表操作(插入、删除、遍历)都必须基于 *Node,传参、返回、赋值时漏掉 * 或 & 会直接导致逻辑错乱或 panic。
立即学习“go语言免费学习笔记(深入)”;
插入节点时,nil 检查和地址取值容易踩的坑
在头部插入时,常有人写 newNode.Next = head 就完事,但若 head 是 nil,这没问题;可一旦后续做 head.Val 就 panic。更隐蔽的是:误把值拷贝当指针赋值,比如:
-
node := *head→ 得到一个新副本,修改它不会影响原链表 -
head = &temp→ 只改了局部变量head,外部指针没变 - 忘记用
&Node{...}或new(Node)创建堆上节点,导致栈变量被回收后指针悬空
安全做法是统一用指针构造:
func InsertHead(head *Node, val int) *Node {
newNode := &Node{Val: val, Next: head}
return newNode
}
遍历链表时,for 循环条件怎么写才不 panic
典型错误是写成 for head != nil && head.Next != nil —— 这适合“处理当前+下一个”的场景(如交换相邻节点),但普通遍历只需判断当前是否为空。多加一层 head.Next != nil 会提前终止,漏掉最后一个节点。
正确遍历模板:
for cur != nil {
fmt.Println(cur.Val)
cur = cur.Next
}
注意:cur = cur.Next 必须在循环体末尾,且不能写成 cur.Next = cur.Next.Next 之类——除非你真想跳过下一节点。另外,别在循环中直接修改 cur 指向的 Val 或 Next,除非你明确知道副作用范围。
反转链表时,三指针法为什么必须用指针变量暂存 next
反转核心是断开旧连接、建立新连接。常见错误是:
- 先执行
cur.Next = prev,再执行cur = cur.Next→ 此时cur.Next已被改成prev,cur.Next不再指向原后续节点,链表断裂 - 用
cur = cur.Next后再取cur.Next,但此时cur可能已是nil,解引用 panic
必须用临时变量保留下一节点地址:
func Reverse(head *Node) *Node {
var prev *Node
cur := head
for cur != nil {
next := cur.Next // ✅ 先保存
cur.Next = prev // 再反转
prev = cur
cur = next // 再移动
}
return prev
}
这里 next 是 *Node 类型,不是 Node;如果误写成 next := *cur.Next,就变成值拷贝,后续 cur = next 会失效。
* 或 & 是在读内容、取地址,还是传引用?多数 bug 就消掉了。










