for 循环
本教程共 80 篇 · 第 19 篇 · 更新于 2026-07-27 · 约 7 分钟阅读
19. for 循环
本节目标:掌握 Go for 循环的三种形式,学会 for-range 遍历各种集合,理解为什么 Go 只保留了一个循环关键字。
Go 只有 for
Go 没有while、do-while,只有 for。但 for 能覆盖所有循环场景,设计上更简洁。
三种 for 形式
形式一:C 风格三段式
for init; condition; post {
// 循环体
}
init:循环开始前执行一次condition:每次迭代前判断,为 false 就退出post:每次迭代后执行
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 输出:0 1 2 3 4
三个部分都可以省略,但分号不能省(除非只剩条件)。
形式二:类似 while
省略 init 和 post,只保留条件:
n := 1
for n < 100 {
n *= 2
}
fmt.Println(n) // 128
这就是其他语言的 while (n < 100)。
形式三:无限循环
全部省略就是死循环:
for {
fmt.Println("永远运行")
}
需要用 break 跳出:
count := 0
for {
count++
if count >= 10 {
break
}
}
fmt.Println(count) // 10
多变量 for
for 可以同时初始化和操作多个变量:
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
fmt.Println(i, j)
}
输出:
0 10
1 9
2 8
3 7
4 6
for-range 遍历
for-range 是 Go 里遍历集合最常用的方式:
for index, value := range collection {
// 使用 index 和 value
}
遍历切片
nums := []int{10, 20, 30, 40, 50}
for index, value := range nums {
fmt.Printf("索引:%d,值:%d\n", index, value)
}
输出:
索引:0,值:10
索引:1,值:20
索引:2,值:30
索引:3,值:40
索引:4,值:50
遍历数组
数组的遍历和切片一样:
arr := [3]string{"Go", "Python", "Rust"}
for i, v := range arr {
fmt.Printf("%d: %s\n", i, v)
}
遍历字符串
for-range 遍历字符串时,按 Unicode 字符(rune)遍历:
s := "Hello,世界"
for i, r := range s {
fmt.Printf("位置:%d,字符:%c\n", i, r)
}
输出:
位置:0,字符:H
位置:1,字符:e
...
位置:6,字符:世
位置:9,字符:界
注意位置不连续,因为中文字符占 3 字节。
遍历 map
m := map[string]int{
"Go": 2009,
"Python": 1991,
"Rust": 2010,
}
for key, value := range m {
fmt.Printf("%s: %d\n", key, value)
}
Notemap 的遍历顺序是随机的,每次运行可能不同。这是 Go 刻意的设计,防止你依赖遍历顺序。
省略索引或值
只需要值,不需要索引:
for _, value := range nums {
fmt.Println(value)
}
只需要索引,不需要值:
for index := range nums {
fmt.Println(index)
}
两个都不需要(只循环次数):
for range nums {
fmt.Println("执行一次")
}
Tip不需要的变量用
_丢弃,这是 Go 的惯例。不要声明了不用,编译器会报错。
嵌套循环
循环可以嵌套:
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d*%d=%-4d", j, i, i*j)
}
fmt.Println()
}
输出九九乘法表:
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
...
break 和 continue
break
break 跳出当前循环:
for i := 0; i < 10; i++ {
if i == 5 {
break // i 等于 5 时退出循环
}
fmt.Println(i)
}
// 输出:0 1 2 3 4
continue
continue 跳过本次迭代,进入下一次:
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // 跳过偶数
}
fmt.Println(i)
}
// 输出:1 3 5 7 9
Note
break和continue只对最内层循环生效。嵌套循环里,内层的break不会退出外层循环。要退出外层需要用标签,下一节讲。
实际示例
累加求和
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println(sum) // 5050
查找元素
nums := []int{5, 3, 8, 1, 9, 2}
target := 8
for i, v := range nums {
if v == target {
fmt.Printf("找到 %d,在位置 %d\n", v, i)
break
}
}
过滤数据
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var evens []int
for _, v := range nums {
if v%2 != 0 {
continue // 跳过奇数
}
evens = append(evens, v)
}
fmt.Println(evens) // [2 4 6 8 10]
模拟 while 读取输入
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
if line == "quit" {
break
}
fmt.Println("你输入了:", line)
}
循环变量的作用域
在 Go 1.22 之前,for 循环的变量在整个循环中只创建一次,每次迭代复用同一个变量。这导致一个经典的闭包陷阱。
Go 1.22 开始,每次迭代都创建新的变量:
// Go 1.22+ 每次迭代 i 是新变量
for i := 0; i < 3; i++ {
go func() {
fmt.Println(i) // 每个 goroutine 打印不同的值
}()
}
这个变化很重要,下一节会详细讲。
常见问题
死循环忘了 break
for {
// 如果没有 break 或 return,永远停不下来
}
无限循环一定要确保有退出条件。
遍历 map 依赖顺序
// 不要这样做!map 遍历顺序是随机的
for k, v := range m {
results = append(results, k)
}
如果需要有序遍历,先把 key 取出来排序:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
修改遍历中的切片
for-range 遍历的是切片的副本,修改 value 不影响原切片:
nums := []int{1, 2, 3}
for _, v := range nums {
v *= 2 // 不影响 nums
}
fmt.Println(nums) // [1 2 3]
要修改原切片,用索引:
for i := range nums {
nums[i] *= 2
}
fmt.Println(nums) // [2 4 6]
学完这节你能做什么
- 用三种形式的 for 循环
- 用 for-range 遍历切片、数组、字符串、map
- 省略不需要的索引或值
- 用 break 和 continue 控制循环
- 避免遍历中修改切片的陷阱
下一节讲 range 的新遍历方式(Go 1.22 range over int 和 1.23 range over func)。