go中链表节点必须用指针,因类型不可直接递归嵌套;next字段须为*node,否则报invalid recursive type;遍历时须判空,函数中修改node.next生效而node=...不生效。

Go 中链表节点必须用指针,否则递归结构编译不通过
Go 编译器不允许类型直接包含自身(比如 type Node struct { next Node }),因为这会导致无限大小的类型。所以链表节点里的 next 字段必须是指向自身的指针:next *Node。这不是风格选择,是语言硬性限制。
常见错误现象:invalid recursive type Node —— 一定义就报这个错,说明你写了值类型嵌套。
-
type Node struct { data int; next Node }→ 错误:非法递归类型 -
type Node struct { data int; next *Node }→ 正确:指针不参与类型大小计算 - 哪怕只在结构体里写
prev Node(哪怕没用),也会触发该错误
new(Node) 和 &Node{} 的内存分配行为其实一致,但语义不同
两者都返回 *Node,底层都调用堆分配(除非逃逸分析优化到栈),但初始化语义有差别:前者字段全零值,后者可显式设初值。
使用场景:构造头节点或临时节点时,&Node{data: 42} 更直观;纯占位或延迟赋值用 new(Node) 也行,但实践中几乎没人这么写。
立即学习“go语言免费学习笔记(深入)”;
-
head := new(Node)→head.data == 0,head.next == nil -
head := &Node{data: 1}→ 同样是零值初始化next,但data显式为 1 - 不要写
var n Node; ptr := &n来模拟节点——局部变量生命周期短,返回其地址可能引发悬垂指针(虽然 Go 逃逸分析通常会自动抬升到堆)
遍历链表时对 next 解引用前必须判空,nil 指针解引用 panic
Go 不做隐式空检查,cur.next.data 这种写法一旦 cur.next == nil 就直接 panic:panic: runtime error: invalid memory address or nil pointer dereference。
性能影响:加一次 != nil 判断开销极小,但漏掉它会让程序在边界条件(空链表、末尾节点)下崩溃。
- 正确写法:
for cur != nil { ...; cur = cur.next } - 错误写法:
for cur.next != nil { cur = cur.next }→ 跳过最后一个有效节点,且如果初始cur == nil会 panic - 递归遍历时同样要先检查
node == nil,再访问node.data或递归调用traverse(node.next)
链表操作中“修改指针指向”和“修改指针所指内容”必须分清
这是最易混淆的点:传入函数的是 *Node,但函数内改 node = &Node{...} 不会影响调用方,而改 node.next = ... 会生效。
原因:Go 所有参数都是值传递,*Node 是指针值,复制的是地址本身;改地址副本不影响原地址,但通过该地址修改堆上数据是全局可见的。
-
func insertHead(head *Node, v int) *Node { return &Node{data: v, next: head} }→ 必须返回新头,不能试图“修改 head 变量本身” -
func deleteNext(node *Node) { if node.next != nil { node.next = node.next.next } }→ 这个修改生效,因为改的是node指向的对象的字段 - 想原地插入?得传 **Node,比如
func insertAfter(pp **Node, v int),否则无法改变调用方持有的指针值
Node 是堆上一块连续内存,next 字段存的是另一个 Node 的地址。真正容易被忽略的是——所有涉及指针赋值、解引用、函数传参的地方,都要反复问自己:我是在改“指针变量”,还是在改“指针指向的值”,还是在改“指针指向的值里的某个字段”。这三个层次差一点,行为就完全不同。










