
go 中 `for` 循环的初始化语句仅执行一次,而更新语句(post statement)在每次迭代末尾执行;原 `iterate` 函数错误地将 `head` 更新为旧的 `n`,却未重新计算 `n`,导致无限循环。
在 Go 中遍历单链表看似简单,但若对 for 循环的三段式结构(for init; condition; post)理解不深,极易陷入逻辑陷阱。问题代码中的 iterate 函数:
func iterate(head *Node) {
for n := head.Next; n != nil; head = n {
fmt.Printf("head %+v n %+v\n", head, n)
time.Sleep(time.Second * 1)
}
}表面看,head = n 应使 head 指向下一个节点,从而让下一轮 n := head.Next 获取后续节点——但这是误解。关键在于:n := head.Next 是初始化语句(init),只在循环开始前执行一次;后续迭代中,n 的值不会自动更新,它始终是第一次计算出的 head.Next(即第二个节点),而 head = n 又把 head 覆盖为该固定地址,造成 head.Next 永远指向自身(或同一后续节点),形成死循环。
✅ 正确做法有两类:
方案一(推荐):直接以 head 为遍历变量(最简洁、惯用)
func iterate(head *Node) {
for ; head != nil; head = head.Next {
fmt.Printf("Node: %+v\n", head)
// time.Sleep(time.Second * 1) // 仅调试时启用
}
}此写法省略初始化和条件判断的冗余变量,清晰表达“只要 head 非空,就处理当前节点并前进”,符合 Go 的简洁哲学。
方案二:显式维护 current 指针(更易读,适合复杂逻辑)
func iterate(head *Node) {
for current := head; current != nil; current = current.Next {
fmt.Printf("Current: %+v\n", current)
}
}与方案一逻辑等价,但分离了“访问变量”与“移动逻辑”,便于在循环体内插入条件跳过、修改指针等操作。
⚠️ 注意事项:
- 避免在 for 初始化中计算依赖于循环变量的表达式(如 n := head.Next),除非你明确知道它只执行一次;
- 若需在循环中动态更新多个变量,应将更新逻辑放入循环体末尾(如 n = head.Next),而非依赖 post 语句;
- 调试链表时,建议打印节点地址(%p)和 Next 字段值,快速识别是否出现环或指针错乱;
- 实际项目中,可考虑封装为方法(如 func (h *Node) Iterate(fn func(*Node)))提升复用性。
总结:Go 的 for 循环不是“每次重算初始化”,而是“一次初始化 + 多次条件判断 + 多次后置更新”。掌握这一机制,才能写出健壮、可维护的链表遍历逻辑。










