Go 从 0 到精通 · 第 05 课:循环结构 for

学习定位:这是整套 Go 教程的第 5 课。
前置要求:已经完成第 4 课,掌握了 ifelse ifelseswitch 以及逻辑运算符的使用。
本课目标:掌握 Go 中唯一的循环关键字 for 的所有常见用法,理解 breakcontinue 的控制方式,初步接触 for range 遍历。


1. 本课你要解决的核心问题

前面你学会了让程序"做判断",但程序还不会"重复做事"。循环就是让程序能反复执行某段逻辑的机制。

你需要搞明白以下问题:

  • 为什么 Go 只有 for 一种循环关键字
  • 经典三段式 for 怎么写
  • 条件式 for 怎么模拟其他语言的 while
  • 无限循环怎么写,什么时候用
  • breakcontinue 分别做什么
  • for range 是什么,怎么遍历
  • 循环中有哪些常见坑

学完这一课,你就掌握了 Go 的全部基础控制流,可以写出有判断、有循环的完整程序逻辑了。


2. Go 只有 for,没有 whiledo...while

这是 Go 和很多其他语言最大的区别之一。

在 C、Java、Python 等语言里,循环通常有好几种关键字:forwhiledo...while

Go 只保留了 for 一个关键字,但通过不同的写法,它能覆盖所有这些场景。

Go 为什么这样设计:

  • 减少概念数量,降低心智负担
  • 一种结构多种用法,更统一
  • 符合 Go"少即是多"的设计哲学

你接下来会看到,for 虽然只有一个关键字,但它的表达能力非常强。


3. 经典三段式 for

这是最常见、最基础的循环写法。

3.1 基本语法

1
2
3
for 初始语句; 条件表达式; 后置语句 {
循环体
}

三个部分的含义:

  • 初始语句:循环开始前执行一次,通常用来声明计数器
  • 条件表达式:每次循环前判断,为 true 就继续,为 false 就结束
  • 后置语句:每次循环体执行完后执行,通常用来更新计数器

3.2 最简单的例子

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
for i := 0; i < 5; i++ {
fmt.Println("第", i, "次循环")
}
}

输出:

1
2
3
4
5
 0 次循环
1 次循环
2 次循环
3 次循环
4 次循环

执行流程是:

  1. i := 0:声明变量 i,初始值为 0
  2. 判断 i < 5,成立,执行循环体
  3. 执行完循环体后,执行 i++i 变成 1
  4. 再次判断 i < 5,成立,继续
  5. 重复,直到 i 等于 5,条件不成立,循环结束

3.3 i++ 是什么

i++ 等价于 i = i + 1,表示让 i 的值增加 1

同理,i-- 等价于 i = i - 1

Go 中的 ++-- 有一个重要限制:

i++i-- 是语句,不是表达式。

这意味着你不能写这样的代码:

1
2
// 错误:Go 不允许
j := i++

也不存在 ++i 这种前置写法。Go 只支持后置的 i++i--,而且它们只能单独作为一条语句使用。


3.4 循环变量的作用域

for 初始语句中用 := 声明的变量,作用域仅限于这个 for 循环:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
for i := 0; i < 3; i++ {
fmt.Println(i)
}

// 这里访问 i 会报错
// fmt.Println(i) // 编译错误:undefined: i
}

这和 if 初始化语句的作用域规则一样:变量只在它所属的语句块中有效。


3.5 倒序循环

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
for i := 5; i > 0; i-- {
fmt.Println(i)
}
}

输出:

1
2
3
4
5
5
4
3
2
1

初始语句、条件、后置语句可以根据需要灵活调整。


3.6 步长不是 1

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
for i := 0; i < 20; i += 3 {
fmt.Println(i)
}
}

输出:

1
2
3
4
5
6
7
0
3
6
9
12
15
18

i += 3 表示每次循环后 i 增加 3。你可以用任何合法的赋值语句作为后置语句。


4. 条件式 for:Go 版的 while

4.1 基本写法

如果你省略初始语句和后置语句,只保留条件表达式,for 就变成了其他语言中的 while

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
count := 0

for count < 5 {
fmt.Println("count =", count)
count++
}
}

输出:

1
2
3
4
5
count = 0
count = 1
count = 2
count = 3
count = 4

这等价于其他语言的:

1
2
3
while (count < 5) {
// ...
}

Go 里没有 while 关键字,但 for 条件 { } 完全能替代它。


4.2 典型使用场景

条件式 for 适合循环次数不确定的场景,例如:

  • 不断读取用户输入,直到用户输入特定值
  • 不断尝试某个操作,直到成功
  • 数据处理到某个条件时停止
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
sum := 0
n := 1

for sum < 100 {
sum += n
n++
}

fmt.Println("累加到", n-1, "时,总和首次达到", sum)
}

输出:

1
累加到 14 时,总和首次达到 105

5. 无限循环

5.1 基本写法

如果 for 后面什么都不写,就是无限循环:

1
2
3
for {
// 永远执行
}

这等价于其他语言的 while(true)


5.2 无限循环不一定是 bug

无限循环在很多场景下是正常的设计模式,例如:

  • 服务器持续监听请求
  • 不断等待用户输入
  • 定时任务持续运行

关键是:无限循环通常要配合 breakreturn 来提供退出条件。


5.3 实际例子:用户输入循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
var input string

for {
fmt.Print("请输入命令(输入 quit 退出):")
fmt.Scanln(&input)

if input == "quit" {
fmt.Println("再见!")
break
}

fmt.Println("你输入了:", input)
}
}

运行效果:

1
2
3
4
5
6
请输入命令(输入 quit 退出):hello
你输入了: hello
请输入命令(输入 quit 退出):Go
你输入了: Go
请输入命令(输入 quit 退出):quit
再见!

break 让循环立刻终止,程序继续执行循环后面的代码。


6. break:跳出循环

6.1 基本用法

break 的作用是:立刻终止当前所在的循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
fmt.Println("循环结束")
}

输出:

1
2
3
4
5
6
0
1
2
3
4
循环结束

i 等于 5 时,break 执行,循环立刻结束,程序继续打印"循环结束"。


6.2 break 只能跳出最内层循环

如果有嵌套循环,break 只能跳出它直接所在的那一层循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 2 {
break
}
fmt.Printf("i=%d j=%d\n", i, j)
}
}
}

输出:

1
2
3
4
5
6
i=0 j=0
i=0 j=1
i=1 j=0
i=1 j=1
i=2 j=0
i=2 j=1

j == 2 时,break 只跳出内层循环,外层循环继续。

如果需要跳出外层循环,可以使用标签(label),下面会讲。


7. continue:跳过本次,进入下一次

7.1 基本用法

continue 的作用是:跳过当前这一次循环的剩余部分,直接进入下一次循环。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Println(i)
}
}

输出:

1
2
3
4
5
1
3
5
7
9

i 是偶数时,continue 执行,跳过 fmt.Println(i),直接进入下一次循环。


7.2 breakcontinue 的区别

  • break:直接终止整个循环
  • continue:跳过本次循环的剩余代码,进入下一次循环

用一个比喻来理解:

  • break 是"我不干了,走人"
  • continue 是"这次算了,下一个"

8. 标签(Label):跳出多层循环

8.1 问题场景

有时候你在嵌套循环里需要一次跳出所有层循环,但 break 默认只跳出最内层。

8.2 使用标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i == 1 && j == 1 {
break outer
}
fmt.Printf("i=%d j=%d\n", i, j)
}
}
fmt.Println("全部结束")
}

输出:

1
2
3
4
5
i=0 j=0
i=0 j=1
i=0 j=2
i=1 j=0
全部结束

outer: 是一个标签,放在外层循环前面。break outer 表示跳出带 outer 标签的那层循环。

continue 也可以配合标签使用,表示跳到外层循环的下一次迭代。

标签在实际开发中不算非常常用,但遇到嵌套循环场景时它是一个有用的工具。


9. for range:遍历利器

9.1 什么是 for range

for range 是 Go 中专门用来遍历数据结构的语法。它可以遍历:

  • 字符串
  • 数组
  • 切片
  • map
  • channel

这一课先用字符串来感受它的基本用法,后面学到切片、map 时会深入使用。


9.2 遍历字符串

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
s := "Hello"

for i, ch := range s {
fmt.Printf("下标 %d:字符 %c\n", i, ch)
}
}

输出:

1
2
3
4
5
下标 0:字符 H
下标 1:字符 e
下标 2:字符 l
下标 3:字符 l
下标 4:字符 o

for range 每次迭代会返回两个值:

  • 第一个值:当前元素的索引(下标)
  • 第二个值:当前元素的值

9.3 只需要索引时

如果你只需要索引,不需要值,可以只写一个变量:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
s := "Hello"

for i := range s {
fmt.Println("索引:", i)
}
}

9.4 只需要值时

如果你只需要值,不需要索引,用 _ 忽略索引:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
s := "Hello"

for _, ch := range s {
fmt.Printf("%c ", ch)
}
fmt.Println()
}

输出:

1
H e l l o

_ 是 Go 的空白标识符,表示"我不关心这个值"。


9.5 for range 遍历中文字符串

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
s := "你好Go"

for i, ch := range s {
fmt.Printf("下标 %d:字符 %c\n", i, ch)
}
}

输出:

1
2
3
4
下标 0:字符 你
下标 3:字符 好
下标 6:字符 G
下标 7:字符 o

注意下标不是连续的 0, 1, 2, 3,而是 0, 3, 6, 7

这是因为 for range 遍历字符串时,是按 Unicode 码点(rune)遍历的。中文字符在 UTF-8 编码中通常占 3 个字节,所以下标会跳跃。

这个知识点在后面第 11 课《字符串、byterune》中会深入讲解。你现在先知道这个现象就好。


10. 循环的常见模式

10.1 累加求和

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println("1 到 100 的和是:", sum)
}

输出:

1
1 100 的和是: 5050

10.2 打印九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d×%d=%-4d", j, i, i*j)
}
fmt.Println()
}
}

输出:

1
2
3
4
5
6
7
8
9
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81

这里用了嵌套循环。%-4d 是格式化动词,表示左对齐、占 4 个字符宽度。


10.3 查找特定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
target := 7
found := false

for i := 1; i <= 10; i++ {
if i == target {
fmt.Println("找到了:", i)
found = true
break
}
}

if !found {
fmt.Println("没有找到")
}
}

这是一个很常见的模式:在循环中查找某个值,找到后用 break 退出。


11. 一段综合示例

把本课的核心知识串到一个程序里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import "fmt"

func main() {
// 经典 for 循环
fmt.Println("=== 经典 for ===")
for i := 1; i <= 5; i++ {
fmt.Printf("第 %d 次\n", i)
}

// 条件式 for(模拟 while)
fmt.Println("\n=== 条件式 for ===")
n := 1
for n <= 32 {
fmt.Print(n, " ")
n *= 2
}
fmt.Println()

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

// continue 跳过偶数
fmt.Println("\n=== continue 跳过偶数 ===")
for i := 1; i <= 10; i++ {
if i%2 == 0 {
continue
}
fmt.Print(i, " ")
}
fmt.Println()

// for range 遍历字符串
fmt.Println("\n=== for range ===")
for _, ch := range "Go语言" {
fmt.Printf("%c ", ch)
}
fmt.Println()
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
=== 经典 for ===
第 1 次
第 2 次
第 3 次
第 4 次
第 5 次

=== 条件式 for ===
1 2 4 8 16 32

=== 无限循环 ===
count = 1
count = 2
count = 3

=== continue 跳过偶数 ===
1 3 5 7 9

=== for range ===
G o 语 言

12. 常见坑总结

12.1 死循环忘记更新条件

1
2
3
4
5
6
// 危险:count 永远不会变
count := 0
for count < 5 {
fmt.Println(count)
// 忘了写 count++
}

这会导致程序永远不停,必须强制终止。

写条件式 for 时,一定要确保循环体内有改变条件的逻辑。


12.2 三段式 for 的条件写错导致不执行

1
2
3
4
// 循环体一次都不执行
for i := 10; i < 5; i++ {
fmt.Println(i)
}

因为初始值 10 已经不满足 i < 5,循环体直接跳过。

这不是报错,而是逻辑问题,调试时需要注意。


12.3 在循环中修改循环变量

1
2
3
4
5
6
7
// 不推荐这样写
for i := 0; i < 10; i++ {
if i == 5 {
i = 8 // 手动修改循环变量
}
fmt.Println(i)
}

虽然 Go 允许这样做,但手动在循环体内修改循环变量会让代码难以理解和维护。

除非有明确的理由,否则不要在循环体内修改三段式 for 的计数器。


12.4 for range 的值是拷贝

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
s := "abc"

for _, ch := range s {
fmt.Printf("%T ", ch)
}
fmt.Println()
}

输出:

1
int32 int32 int32

for range 遍历字符串时,每个字符的类型是 int32(也就是 rune),而不是 byte

而且你拿到的值是一份拷贝,修改它不会影响原始数据。这个概念在后面学切片时会更加重要。


12.5 以为 Go 有 while 关键字

Go 没有 while。如果你写了 while,编译器会直接报错。

for 条件 { } 来替代 while


12.6 breakcontinue 搞混

  • break:终止整个循环
  • continue:跳过本次,继续下一次

如果你想的是"跳过当前这个,继续处理下一个",用 continue
如果你想的是"不再循环了,结束",用 break


13. 本课练习

一定要亲手写,不要只看。

练习 1:打印 1 到 20

要求:

  • 用经典三段式 for 打印 1 到 20 的所有整数
  • 每个数字占一行

练习 2:累加求和

要求:

  • 计算 1 + 2 + 3 + … + 100 的和
  • 打印结果

练习 3:只打印奇数

要求:

  • 打印 1 到 50 之间的所有奇数
  • continue 跳过偶数

练习 4:猜数字游戏

要求:

  • 程序内部固定一个"答案"数字(比如 42
  • 用无限循环不断读取用户输入
  • 如果猜对了,打印"恭喜你猜对了!"并退出
  • 如果猜大了,提示"太大了"
  • 如果猜小了,提示"太小了"

练习 5:九九乘法表

要求:

  • 用嵌套循环打印九九乘法表
  • 格式要对齐整齐

练习 6:用 for range 遍历字符串

要求:

  • 定义一个包含中文和英文的字符串,例如 "Hello你好"
  • for range 遍历,打印每个字符和它的下标
  • 观察中文字符的下标是否连续,自己解释原因

练习 7:找出 100 以内的所有素数

要求:

  • 打印 2 到 100 之间的所有素数
  • 用嵌套循环判断一个数是否是素数
  • 素数的定义:只能被 1 和它自身整除的大于 1 的整数

提示:内层循环从 2i-1,如果都不能整除,就是素数。


练习 8:break 标签练习

要求:

  • 写一个双层嵌套循环
  • 在内层循环中使用 break 加标签,一次跳出两层循环
  • 打印跳出后的信息

14. 自测题

不看文档,试着回答:

14.1 概念题

  1. Go 有几种循环关键字?
  2. 经典三段式 for 的三个部分分别是什么?
  3. 怎么用 for 模拟其他语言的 while
  4. 怎么写无限循环?
  5. breakcontinue 的区别是什么?
  6. break 在嵌套循环中默认跳出几层?
  7. 怎么用标签跳出多层循环?
  8. for range 遍历字符串时,每次迭代返回什么?
  9. i++ 在 Go 中是表达式还是语句?
  10. 为什么 for range 遍历中文字符串时下标不连续?

如果你能流畅回答这些问题,说明这一课你已经真正理解了。


15. 本课总结

这一课你学到的是让程序具备"重复执行"能力的核心机制。

你现在应该已经理解:

  • Go 只有 for 一种循环关键字,但能覆盖所有循环场景
  • 经典三段式 for i := 0; i < n; i++ { } 是最基础的用法
  • 省略初始和后置语句的 for 条件 { } 等价于其他语言的 while
  • for { } 是无限循环,通常配合 break 使用
  • break 终止循环,continue 跳过本次
  • break 默认只跳出最内层循环,用标签可以跳出多层
  • for range 用于遍历字符串、数组、切片、map 等数据结构
  • for range 遍历字符串时按 rune(Unicode 码点)遍历,中文下标会跳跃
  • i++ 是语句不是表达式,不存在 ++i 的写法

到这里,你已经掌握了 Go 的全部基础控制流:顺序执行、条件判断(if/switch)、循环(for)。接下来就可以开始学习函数了。


16. 下一课预告

下一课我们进入:函数基础

会重点讲:

  • 函数的定义和调用
  • 参数和返回值
  • 多返回值是什么,为什么 Go 这样设计
  • 命名返回值
  • 可变参数
  • 函数是"一等公民"的初步理解
  • 新手常见坑

学完下一课,你就能把重复的逻辑封装成函数,让代码更有组织、更容易复用了。