Go 语言完全指南 / 06 - 控制流:if、for、switch、select、goto、defer
06 - 控制流
6.1 if 语句
package main
import (
"fmt"
"math"
"os"
)
func main() {
// 基本 if
x := 10
if x > 5 {
fmt.Println("x 大于 5")
}
// if-else
if x > 100 {
fmt.Println("大于 100")
} else if x > 10 {
fmt.Println("大于 10")
} else {
fmt.Println("小于等于 10")
}
// if 初始化语句(变量作用域限定在 if 块内)
if _, err := os.Stat("file.txt"); err == nil {
fmt.Println("文件存在")
} else {
fmt.Println("文件不存在:", err)
}
// err 在此处不可用
// 实际常用模式
if result := math.Sqrt(16); result > 3 {
fmt.Printf("√16 = %.0f > 3\n", result)
}
}
💡 技巧:Go 的 if 不需要括号,但条件两边需要空格。推荐使用 if 初始化语句 模式来限制变量作用域。
6.2 for 循环
Go 只有 for 一个循环关键字,但它能表达所有循环模式。
基本 for 循环
// 经典三段式
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// while 风格
n := 1
for n < 100 {
n *= 2
}
fmt.Println(n) // 128
// 无限循环
for {
// 使用 break 退出
break
}
// 初始化多变量
for i, j := 0, 10; i < j; i, j = i+1, j-1 {
fmt.Printf("i=%d, j=%d\n", i, j)
}
for-range 遍历
func main() {
// 遍历切片
fruits := []string{"苹果", "香蕉", "橘子"}
for i, fruit := range fruits {
fmt.Printf("[%d] %s\n", i, fruit)
}
// 忽略索引
for _, fruit := range fruits {
fmt.Println(fruit)
}
// 忽略值(只遍历索引)
for i := range fruits {
fmt.Println(i)
}
// 遍历 map
scores := map[string]int{"Alice": 95, "Bob": 87, "Carol": 92}
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
// 遍历字符串(遍历 rune)
for i, ch := range "Hello, 世界" {
fmt.Printf("[%d] %c\n", i, ch)
}
// 遍历 channel
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for v := range ch {
fmt.Println(v)
}
}
⚠️ 注意(Go 1.22 之前):for-range 循环变量在每次迭代中复用同一地址。Go 1.22 修复了这个问题,每个迭代有自己的变量副本。
// Go 1.22 之前的陷阱
funcs := make([]func(), 3)
for i := range funcs {
// Go 1.22+:每次迭代 i 是新变量 ✅
// Go 1.21-:所有闭包共享同一个 i ⚠️
funcs[i] = func() { fmt.Println(i) }
}
for _, f := range funcs {
f()
}
// Go 1.22+: 0, 1, 2
// Go 1.21-: 2, 2, 2
break 和 continue
// 带标签的 break 和 continue
outer:
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 3 {
continue // 跳过当前 j
}
if i+j > 10 {
break outer // 跳出整个 outer 循环
}
fmt.Printf("(%d,%d) ", i, j)
}
}
6.3 switch 语句
基本 switch
func main() {
day := "Wednesday"
switch day {
case "Monday":
fmt.Println("星期一")
case "Tuesday":
fmt.Println("星期二")
case "Wednesday":
fmt.Println("星期三")
case "Thursday":
fmt.Println("星期四")
case "Friday":
fmt.Println("星期五")
case "Saturday", "Sunday":
fmt.Println("周末")
default:
fmt.Println("未知")
}
}
无条件 switch
func classify(x int) string {
switch {
case x < 0:
return "负数"
case x == 0:
return "零"
case x > 0 && x <= 10:
return "小正数"
default:
return "大数"
}
}
switch 初始化语句
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS")
case "linux":
fmt.Println("Linux")
default:
fmt.Printf("其他: %s\n", os)
}
类型 switch
func describe(i interface{}) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("整数: %d", v)
case string:
return fmt.Sprintf("字符串: %q", v)
case bool:
return fmt.Sprintf("布尔: %v", v)
case []int:
return fmt.Sprintf("切片: %v", v)
default:
return fmt.Sprintf("未知类型: %T", v)
}
}
fallthrough
func checkNumber(n int) {
switch {
case n > 0:
fmt.Print("正数")
fallthrough // 继续执行下一个 case(不检查条件)
case n%2 == 0:
fmt.Print(" & 偶数")
default:
fmt.Print("其他")
}
fmt.Println()
}
// checkNumber(4) → "正数 & 偶数"
// checkNumber(3) → "正数"
// checkNumber(-2) → "其他"
⚠️ 注意:fallthrough 不检查下一个 case 的条件,直接执行其代码体。大多数情况下不需要使用。
6.4 select 语句
select 用于在多个 channel 操作中选择:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "two"
}()
// 等待最先就绪的 channel
select {
case msg := <-ch1:
fmt.Println("收到:", msg)
case msg := <-ch2:
fmt.Println("收到:", msg)
case <-time.After(3 * time.Second):
fmt.Println("超时")
}
// 收到: two(因为 ch2 先就绪)
}
select 实现非阻塞操作
func nonBlockingSend(ch chan int, value int) bool {
select {
case ch <- value:
return true
default:
return false // channel 已满或未初始化
}
}
func nonBlockingReceive(ch chan int) (int, bool) {
select {
case v := <-ch:
return v, true
default:
return 0, false
}
}
select 随机选择
// 当多个 case 同时就绪时,select 随机选择一个
func randomSelect() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "A"
ch2 <- "B"
// 结果不确定
select {
case v := <-ch1:
fmt.Println(v)
case v := <-ch2:
fmt.Println(v)
}
}
6.5 goto 语句
Go 支持 goto,但有严格限制:不能跳过变量声明。
func main() {
n := 0
loop:
if n < 5 {
fmt.Println(n)
n++
goto loop
}
fmt.Println("Done")
// 实际用例:跳出多层嵌套
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if i*j > 20 {
goto done
}
}
}
done:
fmt.Println("跳出嵌套循环")
}
⚠️ 注意:goto 应当谨慎使用,大多数情况用 break + 标签或函数抽取更好。
6.6 defer 语句
defer 语句将函数调用推迟到外层函数返回之前执行。
基本用法
func main() {
fmt.Println("开始")
defer fmt.Println("延迟执行 1")
defer fmt.Println("延迟执行 2")
defer fmt.Println("延迟执行 3")
fmt.Println("结束")
// 输出:
// 开始
// 结束
// 延迟执行 3
// 延迟执行 2
// 延迟执行 1
}
defer 执行顺序
// defer 是栈式执行(LIFO:后进先出)
func deferOrder() {
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// 输出: 4 3 2 1 0
}
defer 参数求值时机
func main() {
x := 10
defer fmt.Printf("defer 中 x = %d\n", x) // 参数在 defer 语句处求值
x = 20
fmt.Printf("普通 x = %d\n", x)
// 输出:
// 普通 x = 20
// defer 中 x = 10
}
常见使用模式
// 模式1:资源关闭
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // 确保文件关闭
data, err := io.ReadAll(f)
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
// 模式2:修改命名返回值
func divide(a, b int) (result int, err error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// 模式3:计时器
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
fmt.Printf("%s took %s\n", name, elapsed)
}
func slowFunction() {
defer timeTrack(time.Now(), "slowFunction")
time.Sleep(500 * time.Millisecond)
}
// 模式4:panic/recover
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("recovered from panic: %v\n", r)
}
}()
panic("something went wrong")
}
// 模式5:修改返回值
func f() (result int) {
defer func() {
result *= 2 // 可以修改命名返回值
}()
return 10 // 最终返回 20
}
defer 与闭包配合
func processFiles(files []string) error {
for _, file := range files {
f, err := os.Open(file)
if err != nil {
return err
}
// ✅ 正确:每次迭代都 defer Close
// 但要注意:如果有大量文件,这些 defer 会堆积到函数结束
defer f.Close()
}
// ... 处理文件
return nil
}
// 更好的做法:使用闭包函数
func processFilesBetter(files []string) error {
for _, file := range files {
if err := func() error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close() // 在闭包返回时就关闭
// 处理文件...
return nil
}(); err != nil {
return err
}
}
return nil
}
6.7 综合示例:简单的 REPL
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Go REPL (输入 quit 退出)")
for {
fmt.Print("> ")
if !scanner.Scan() {
break
}
input := strings.TrimSpace(scanner.Text())
switch input {
case "quit", "exit":
fmt.Println("再见!")
return
case "time":
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
case "help":
fmt.Println("命令: time, help, quit")
case "":
continue
default:
fmt.Printf("未知命令: %s\n", input)
}
}
}
🏢 业务场景
- 状态机:用 switch 实现订单状态流转
- 超时控制:select + time.After 实现请求超时
- 资源管理:defer 确保连接/文件关闭
- 错误恢复:defer + recover 捕获 panic
- 事件循环:for + select 实现事件驱动模型