强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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)
        }
    }
}

🏢 业务场景

  1. 状态机:用 switch 实现订单状态流转
  2. 超时控制:select + time.After 实现请求超时
  3. 资源管理:defer 确保连接/文件关闭
  4. 错误恢复:defer + recover 捕获 panic
  5. 事件循环:for + select 实现事件驱动模型

📖 扩展阅读