← [ BACK_TO_NOTES ]

Go 流程控制

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

#concept #growing #dev #go

[!info] related notes

Go 流程控制

这个概念为什么会出现

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

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

Go 有意保持控制结构种类少、规则硬:没有 while/do-while,循环统一为 for;switch 默认不 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)
}

保存为 main.go,在已执行 go mod init 的模块目录运行:

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 {
	// ...
}

规则:

  • 条件不要求外层圆括号,但 if (ok) 合法;括号也可控制表达式优先级\
  • 主体必须花括号
  • init 中声明的变量作用域覆盖 if、else if、else
  • 条件必须是布尔类型(含以 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(位于非最后分支的末尾,进入下一分支体且不检查其表达式;type switch 不允许)\
  • switch 也可带 init:switch x := f(); x { ... }
  • 无 tag 的 switch { case cond: } 适合多条件分支
  • case 可以是常量或运行时表达式,须与 switch 表达式类型兼容;依次比较,匹配后不再求值后续 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 i == 0 && j == 0 {
				continue Outer // 进入外层下一轮
			}
		}
	}
  • 无标签 break 结束最内层 for/switch/select;continue 只继续最内层 for,即使它写在 switch/select 内\
  • break 标签必须指向包围它的 for/switch/select;continue 标签只能指向包围它的 for\
  • 标签在 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 及之后语言语义时,循环中声明的变量每轮独立;用 = 复用已有变量不变。语言版本也受模块 go 指令等控制,不能仅看安装的工具链版本;详见 range 语义。

4. 空 switch / 空 for

合法但要意图清晰;服务型无限 for 可以长期运行,但应按生命周期设计退出与等待;阻塞点减少忙等,不等于退出机制。

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 的交界,勿忙等。

以下为分发片段,start、stop 与 fmt 导入由应用提供:

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 专题。
  • 下一步:函数、错误处理、range、type switch。

自测题

概念题

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

代码推理题

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 0、one、i 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/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本