讲解

很多语言有 for、while、do-while 好几种循环,Go 只保留了 for,并用三种写法覆盖全部场景。经典三段式 for i := 0; i < n; i++ 和其他语言一样;只写条件 for n < 100 就相当于 while;什么都不写 for { } 是无限循环,配合 break 退出。少一种语法就少一份记忆负担,这是 Go 设计哲学的缩影。

遍历容器用 for range:对切片遍历拿到下标和值,对 map 遍历拿到键和值,对字符串遍历拿到字节下标和 rune。不需要的部分用 _ 忽略,比如只要值就 for _, v := range s。循环内用 continue 跳过本轮、break 退出整个循环。

有一个历史大坑必须知道:Go 1.22 之前,for range 的循环变量在每次迭代间是复用的,在循环里启动 goroutine 或保存变量地址会全部指向最后一轮的值;Go 1.22 起改为每次迭代独立变量,这个坑在语言层面被填平了。本教程基于 Go 1.24,示例按新语义书写,但阅读老代码时仍要警惕。

示例

for 的三种形态:

package main

import "fmt"

func main() {
	// 经典三段式
	for i := 0; i < 3; i++ {
		fmt.Println("三段式:", i)
	}

	// 只有条件,相当于其他语言的 while
	n := 1
	for n < 8 {
		n *= 2
	}
	fmt.Println("n =", n)

	// 无限循环配合 break
	count := 0
	for {
		count++
		if count >= 3 {
			break
		}
	}
	fmt.Println("循环了", count, "次")
}

for range 遍历与 continue:

package main

import "fmt"

func main() {
	nums := []int{3, 1, 4, 1, 5, 9}
	for i, v := range nums {
		if v == 1 {
			continue // 跳过本轮
		}
		fmt.Printf("下标 %d 值 %d\n", i, v)
	}

	sum := 0
	for _, v := range nums { // 只要值,忽略下标
		sum += v
	}
	fmt.Println("总和:", sum)
}

经典练习 FizzBuzz,顺便复习无表达式 switch:

package main

import "fmt"

func main() {
	for i := 1; i <= 15; i++ {
		switch {
		case i%15 == 0:
			fmt.Println("FizzBuzz")
		case i%3 == 0:
			fmt.Println("Fizz")
		case i%5 == 0:
			fmt.Println("Buzz")
		default:
			fmt.Println(i)
		}
	}
}

常见坑

  • 写 while 关键字:Go 没有 while,for 条件 { } 就是 while。也没有 do-while,至少执行一次的循环用 for { ...; if cond { break } } 表达。
  • range 顺序依赖:range 遍历 map 的顺序是故意随机的,下一章会再强调;遍历切片、数组才是按下标顺序。
  • 在循环里改正在遍历的切片:range 在开始时对容器做一次求值,循环中 append 的新元素不会被遍历到;删除元素则可能跳过元素。需要边遍历边修改,用经典三段式按下标控制。
  • 老代码的循环变量陷阱:Go 1.22 前 go func() { fmt.Println(i) }() 会全部打印最终值。升级老项目时这类隐匿 bug 可能随语义变化暴露,升级后跑一遍测试很重要。

小结

for 三形态覆盖所有循环,range 遍历容器,break/continue 控制流程;Go 1.22 起循环变量每次迭代独立。下一节学习最常用的数据结构:切片。