982 字
约 3 分钟
5
S09-B06 · 控制流:`for` 是唯一的循环
2024-12-31
2026-11-07

适用读者:从 C/Java/Python 转来,找不到 while 的人。
前置:S09-B02。
实测环境:Go 1.22.2(2026-11-05)。
覆盖旧文:A-1458(28) A-1459(34) A-1460(48) A-1461(35) A-1462(19) A-1463(20) A-1464(17) = 201 点。
覆盖扩展:E-S09-007(range 的副本语义)。
上一篇:S09-B05 · map 与 list:零值、随机遍历与删除
下一篇:S09-B07 · 函数与闭包。


1. 一句话结论

Go 只有一个循环关键字 for,但能表达三种形态;switch 默认不穿透(要穿透得写 fallthrough)。

你以为需要 Go 的写法
while (cond) for cond { }
while (true) for { }
do { } while for { ...; if !cond { break } }
for (init; cond; post) for init; cond; post { }

2. 实测:switch 默认不穿透

switch x {           // x = 2
case 1: fmt.Print("one ")
case 2: fmt.Print("two ")
    fallthrough
case 3: fmt.Print("three ")
}

实测输出:two three

行为 说明
默认 命中一个 case 就结束(不穿透)
fallthrough 显式跳到下一个 case(不再判断条件)
多值 case case 1, 2:
无表达式 switch switch { case x > 0: }(相当于 if-else 链)
类型 switch switch v := i.(type) { case int: }

"默认不穿透"省掉了 C 语言里最容易忘的 break,这就是"收敛选择"的又一个例子。


3. for range 的副本语义(E-S09-007)

type P struct{ X int }
ps := []P{{1}, {2}}
for _, p := range ps { p.X = 99 }     // 改的是副本
fmt.Println(ps)                        // [{1} {2}]  ← 没变

for i := range ps { ps[i].X = 99 }     // 按下标改
fmt.Println(ps)                        // [{99} {99}]
写法 效果
for _, p := range ps p 是元素副本,改它无效
for i := range ps 通过 ps[i] 修改有效
for i := range ps + &ps[i] 指针修改有效

这是 Go 新手最高频的 bug 之一:以为 range 给的是引用。

例外:如果元素本身是"含指针的结构"(如 []*P),改 p.X 会生效——因为改的是指针指向的对象。


4. break / continue 与标签

outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if j == 1 { continue outer }   // 跳到外层下一轮
        if i == 2 { break outer }      // 跳出外层
    }
}
语句 作用
break 跳出最内层 for/switch/select
continue 进入最内层下一轮
break label 跳出指定层
goto label 跳到同函数内的标签(不要跨层用)

goto 的边界:不能跳到另一个函数、不能跳过变量声明。实践里几乎不用——它存在主要是给编译器/生成代码用。


5. 五个误区

误区 真相
"Go 有 while" 没有;用 for cond 表达
"switch 会穿透" 默认不穿透,要 fallthrough
"range 给的是引用" 是副本(除指针/引用类型元素)
"break 能跳出两层" 只能跳最内层,多层要标签
"goto 可以随意用" 限制多、可读性差,实践不用

6. 自检

  1. 用 for 写出 while (i < n) 的等价形式。
  2. switch 默认是否穿透?如何穿透?
  3. for _, p := range ps { p.X = 1 } 为什么不生效?怎么改?
  4. break 与 break label 的区别?
  5. 无表达式 switch { case x>0: } 等价于什么?

7. 下一篇

S09-B07 · 函数、闭包与"循环变量捕获"

控制流讲完,下一篇是最容易出"看起来对但结果错"的地方:闭包捕获循环变量。会给出实测的三种写法对比。


本篇属于系列《S09 · Go 语言》。旧文来源:A-1458–A-1464;扩展点 E-S09-007。switch 与 range 行为为 Go 1.22.2 实测。

S09-B06 · 控制流:`for` 是唯一的循环
https://www.clxhxhhr.top/posts/5304/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。