Go 流程控制

Go 的 if(含简单语句)、for 的多种形态、switch 默认不贯穿,以及 break/continue 与标签;强调早返回与扁平控制流。

#type / concept #status / growing #tech / dev #resource / go

[!info] 关联笔记

Go 流程控制

这个概念为什么会出现

任何非玩具程序都要表达:

  • 条件成立时走哪条路
  • 重复做某事直到条件变化
  • 按离散情况分支
  • 从多层循环中跳出

Go 有意保持控制结构种类少、规则硬:没有 while/do-while,循环统一为 forswitch 默认 fallthrough;条件外无需括号。这降低了方言分叉,也让错误处理里的“早返回”成为自然风格。

[!abstract] 一句话理解 Go 用 if/for/switch 表达几乎所有同步控制流:if/switch 可带简单初始化语句,for 一种关键字覆盖三种循环,switch 默认不贯穿,标签配合 break/continue 可跳出外层。

最小可运行示例

先把示例放进业务场景,再看代码:

场景:成绩单服务的评级与批处理

教务接口要做三件事:

  1. 单科评级:分数 → A/B/C,非法分数直接拒绝
  2. 重试计数循环:某种同步任务最多跑 3 次(for 三要素)
  3. 倒计时/while 形态:队列里还有任务就继续消费(for condition
  4. 汇总:把本页三条分数加总(for range

Go 没有 while,全靠 for 的不同写法;switch 默认不 fallthrough,适合评级这种互斥区间。

package main

import "fmt"

// letterGrade 模拟“百分制转等级”。
// 业务意图:非法输入早返回;合法输入用清晰区间分支。
// 教学点:switch 无表达式 ≈ if-else 链;case 自动结束,不必写 break。
func letterGrade(score int) string {
	if score < 0 || score > 100 {
		return "invalid"
	}
	switch {
	case score >= 90:
		return "A"
	case score >= 80:
		return "B"
	default:
		return "C-or-below"
	}
}

func main() {
	// if 短语句:g 的作用域仅限 if/else,不污染后面代码
	if g := letterGrade(95); g != "invalid" {
		fmt.Println("grade:", g) // A
	}

	// for 三要素:等价“重试/页码”类计数循环
	for i := 0; i < 3; i++ {
		fmt.Println("attempt", i)
	}

	// for 条件:等价其他语言 while —— 还有剩余任务就继续
	left := 3
	for left > 0 {
		fmt.Println("jobs left", left)
		left--
	}

	// for range:汇总本页分数
	sum := 0
	for _, v := range []int{1, 2, 3} {
		sum += v
	}
	fmt.Println("page sum", sum)
}

建议运行:

go run .

期望输出:

grade: A
attempt 0
attempt 1
attempt 2
jobs left 3
jobs left 2
jobs left 1
page sum 6

结合场景再看三个关注点

  1. if 短语句限制作用域
    g := letterGrade(...) 只在分支里可见,避免临时变量漏到函数后半段。

  2. 只有 for,三种循环形态
    计数、while 条件、range 遍历,关键字都是 for

  3. switch 默认不贯穿
    命中 >=90 不会掉进 >=80;需要贯穿时才显式 fallthrough

核心概念与准确模型

if:条件与简单语句

if init; condition {
	// ...
} else if condition2 {
	// ...
} else {
	// ...
}

规则:

  • 条件加圆括号(需要时用括号做运算优先级)
  • 主体必须花括号
  • init 中声明的变量作用域覆盖 ifelse ifelse
  • 布尔条件必须是 bool,无“非零即真”

与错误处理强绑定的形态:

if err := work(); err != nil {
	return err
}

for:三种形态,一个关键字

// 1) 三段式(类 C)
for init; condition; post {
}

// 2) 条件循环(类 while)
for condition {
}

// 3) 无限循环
for {
	break
}

range 形式:

for i, v := range coll {
}
for i := range coll {
}
for range coll { // 只要次数/遍历副作用
}

range 的复制、变量语义、map 迭代顺序等见 range 语义。本篇只把它当作 for 的一种形态。

switch:默认不贯穿

switch tag {
case a, b: // 多值
	// ...
case c:
	// ...
default:
	// ...
}

关键规则:

  • case 末尾默认 break,不会掉进下一 case
  • 需要贯穿时必须显式 fallthrough(立即进入下一 case 体,不再检查下一 case 表达式)
  • switch 也可带 init:switch x := f(); x { ... }
  • 无 tag 的 switch { case cond: } 适合多条件分支
  • case 表达式常量通常需类型兼容;运行期按相等比较

类型相关的分支使用 type switch,语法为 switch x := v.(type),详见 类型断言与 type switch,不要与值 switch 混为一谈。

break / continue 与标签

Outer:
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if i == 1 && j == 1 {
				break Outer // 跳出外层 for
			}
			if j == 0 {
				continue Outer // 进入外层下一轮
			}
		}
	}
  • 无标签时,break/continue 作用于最内层 for/switch/select
  • break 也可带标签跳出指定的 for/switch/select
  • 标签在 Go 中少用;优先考虑拆函数、早返回、goto 更少用

goto(仅作边界了解)

Go 有 goto,但规范与社区实践都强烈限制使用;可读的结构化控制流优先。本课不展开。

defer 的关系

defer 注册的调用在函数返回前执行,常与错误路径、资源释放交织。它改变的是退出路径上的调用顺序,不是新的分支语法。见 defer/panic/recover

设计动机

  1. 少即是多
    一种循环关键字,减少 while/do 变体。
  2. switch 更安全
    默认不 fallthrough,避免 C 系经典 bug。
  3. 简单语句作用域
    if err := ...; err != nil 把临时变量关在分支里。
  4. 服务错误处理风格
    扁平、早返回、显式分支,而不是表达式里塞复杂逻辑。

边界情况与反直觉行为

1. switch 的 fallthrough 很“硬”

fallthrough 不判断下一个 case 条件,直接执行下一 case 体。

2. break 在 switch 内

for { switch { case: break } } 中,无标签 break 只离开 switch,不离开 for。

3. range 变量语义随版本

循环变量每轮是否“新变量”在 Go 1.22 有重要变化;写闭包捕获时必须查 range 语义

4. 空 switch / 空 for

合法但要意图清晰;无限 for 必须有可靠退出(breakreturn、阻塞点)。

5. case 中声明

case 里用 := 时注意作用域与阴影,保持短小。

常见误区

[!warning] 常见误区:以为 switch 会像 C 一样自动贯穿 错误:写多个 case 指望掉下去。
正确:默认独立;需要时显式 fallthrough 或合并 case a, b:

[!warning] 常见误区:用 while 关键字 错误:找 while
正确:for condition { }

[!warning] 常见误区:深层嵌套代替早返回 错误:if ok { if err==nil { if ... } }
正确:失败条件先 return/continue,主路径留在低缩进。

[!warning] 常见误区:break 期望跳出外层 for 错误:在内层 switch/for 写 break 以为结束全部循环。
正确:标签 break Outer 或重构函数。

[!warning] 常见误区:把 type switch 当普通 switch 学完即止 错误:忽略 .(type) 形态。
正确:接口动态类型分支见专题笔记。

与相邻概念对比

概念差异
运算符/表达式产出值;控制流选择语句路径
函数返回最强的“跳出”与错误传播手段
select在 channel 通信上等待,不是值 switch
type switch按接口动态类型分支
defer绑定函数退出,不替代 if/for

工程实践

  1. 错误路径早返回
    错误处理 一致,保持主逻辑直读。
  2. 条件保持纯粹
    复杂条件提取为函数或布尔变量命名。
  3. 循环边界写清楚
    优先 range 遍历集合;需要索引写入时再下标循环。
  4. switch 优于长 else-if
    离散状态、命令字、HTTP 方法等用 switch。
  5. 标签克制
    出现多层 break 标签时,考虑把内层提成函数。
  6. 测试分支
    表驱动覆盖每个 case/default 与循环终止条件。
  7. 并发退出
    循环中检查 ctx.Done() 属于控制流与 context 的交界,勿忙等。
func handle(cmd string) error {
	switch cmd {
	case "start":
		return start()
	case "stop":
		return stop()
	default:
		return fmt.Errorf("unknown cmd %q", cmd)
	}
}

可验证实验

实验 1:if 简单语句作用域

if x := 1; x > 0 { } 外打印 x,确认编译失败。

实验 2:for 三种形态

分别用三段式、条件式、无限 + break 打印 0..2。

实验 3:switch 不贯穿

连续两个 case 各打印一行,验证只执行匹配的一个;再加 fallthrough 对比。

实验 4:标签 break

双层循环,内层 break Outer 后观察外层是否继续。

实验 5:switch 内 break 与 for

for { switch { case true: break } } 是否终止 for?如何改成终止 for?

本节总结

  • 本质:少量语句覆盖条件、循环、多路分支。
  • 关键规则:if/switch 可 init;for 统一循环;switch 默认不贯穿;标签增强 break/continue。
  • 最易错:fallthrough 预期、break 层次、滥用嵌套、忽略 type switch 专题。
  • 下一步函数错误处理rangetype switch

自测题

概念题

  1. 为什么 Go 没有 while?如何写等价循环?
  2. switch 默认是否 fallthrough?如何显式贯穿?
  3. if err := f(); err != nilerr 的作用域到哪?

代码推理题

for i := 0; i < 2; i++ {
	switch i {
	case 0:
		break
	case 1:
		fmt.Println("one")
	}
	fmt.Println("i", i)
}

会打印什么?若要在 i==0 时直接结束 for,怎么改?

工程思考题

处理嵌套 JSON 结构时写了三层 for + switch,内层多处 break 行为不符合预期。优先重构策略是什么?

参考答案

展开
  1. 设计上合并循环形态;for condition { } 即 while。
  2. 默认不贯穿;使用 fallthrough 语句。
  3. 覆盖整个 if-else 链,出链不可见。
    代码题:break 只离开 switch,仍打印 i 0onei 1。要结束 for:标签 break Loop,或 return,或把条件提到 for。
    工程题:内层提函数并用返回值表达“继续/停止/错误”;少用标签;表驱动分发替换深 switch。

延伸阅读与资料来源

资料类型支撑
Spec — Statements规范if/for/switch/break 等
Spec — If statements规范简单语句与作用域
Spec — For statements规范for / range
Spec — Switch statements规范表达式 switch 与 fallthrough
Tour — Flow control教程入门练习
Effective Go — Control structures官方文档风格与 if/switch 习惯

笔记元信息

  • 建议文件名:go-control-flow.md
  • 所属阶段:语言基础
  • 学习顺序:变量/类型之后,函数与错误处理之前或并行
  • 建议下一篇:函数错误处理
  • 本篇状态:已深化(type switch 仅链接专题;range 细节外置)
创建于 2026/6/20 更新于 2026/7/15