if、for、switch、defer——Go 的控制流语句


一、if 条件语句

1.1 基本语法

// Go 的 if 条件不需要括号,但花括号必须存在
if score >= 60 {
    fmt.Println("pass")
} else if score >= 80 {
    fmt.Println("good")
} else {
    fmt.Println("fail")
}

1.2 if 带初始化语句

Go 的 if 可以包含一条简短的初始化语句,变量作用域仅限于 if/else 块内:

// 经典用法:同时获取值和错误
if err := doSomething(); err != nil {
    fmt.Println("error:", err)
    return
}
// 这里 err 不可用

// 读取文件的经典模式
if data, err := os.ReadFile("config.txt"); err != nil {
    fmt.Println("读取失败:", err)
} else {
    fmt.Println("内容:", string(data))
}

Go 的惯例:if err != nil 是 Go 代码中出现频率最高的模式之一。


二、for 循环

Go 只有 for 一种循环语句,但功能覆盖了 while、do-while 的场景。

2.1 三种形式

// 形式1:传统 for 循环
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// 形式2:while 风格(只有条件)
for condition {
    // ...
}

// 形式3:无限循环
for {
    // break 退出
    if done {
        break
    }
}

2.2 遍历集合

// 遍历切片/数组
nums := []int{10, 20, 30}
for i, v := range nums {
    fmt.Printf("索引=%d 值=%d\n", i, v)
}

// 只要值
for _, v := range nums {
    fmt.Println(v)
}

// 只要索引
for i := range nums {
    fmt.Println(i)
}

// 遍历 map
m := map[string]int{"Go": 1, "Java": 2}
for k, v := range m {
    fmt.Printf("%s → %d\n", k, v)
}

// 遍历字符串(按 rune 遍历)
for i, ch := range "Hello, 世界" {
    fmt.Printf("字节位置=%d 字符=%c Unicode=%U\n", i, ch, ch)
}
// 输出中 '世' 的索引是 7(不是 10),因为 range 按 rune 遍历

2.3 for-range 陷阱

// 陷阱:range 的值是副本,修改不影响原切片
nums := []int{1, 2, 3}
for _, v := range nums {
    v = v * 2  // ❌ 只修改了副本,nums 不变
}

// 正确做法:通过索引修改
for i := range nums {
    nums[i] = nums[i] * 2  // ✅ 修改原切片
}

三、switch 多分支

Go 的 switch 比其他语言更强大,默认不穿透(fall through)。

3.1 基本用法

// 基本 switch
switch day {
case "Monday":
    fmt.Println("周一")
case "Tuesday":
    fmt.Println("周二")
case "Wednesday", "Thursday", "Friday":  // 多个值匹配同一分支
    fmt.Println("工作日")
default:
    fmt.Println("周末")
}

3.2 无需条件的 switch

// 替代冗长的 if-else 链
switch {
case score >= 90:
    grade = "A"
case score >= 80:
    grade = "B"
case score >= 70:
    grade = "C"
case score >= 60:
    grade = "D"
default:
    grade = "F"
}

3.3 带初始化语句

// 类似 if,switch 也可以带初始化语句
switch os := runtime.GOOS; os {
case "linux":
    fmt.Println("Linux 系统")
case "windows":
    fmt.Println("Windows 系统")
case "darwin":
    fmt.Println("macOS 系统")
default:
    fmt.Println("其他:", os)
}

3.4 fallthrough

// Go 的 switch 默认不穿透,需要用 fallthrough 显式穿透
switch n {
case 1:
    fmt.Println("one")
    fallthrough  // 强制执行下一个 case 的代码(不判断条件)
case 2:
    fmt.Println("two")  // case 1 时也会执行到这里
case 3:
    fmt.Println("three")
}

注意:fallthrough 只能穿透下一个 case,不能穿透多个。实际开发中很少使用。


四、defer — 延迟执行

defer 将函数调用延迟到当前函数返回前执行,是 Go 资源管理的核心机制。

4.1 基本用法

func readFile(path string) {
    file, err := os.Open(path)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()  // 函数返回前一定会执行

    // 使用 file 进行读取操作
    data := make([]byte, 1024)
    file.Read(data)
    fmt.Println(string(data))
    // 不需要手动关闭,defer 会在函数返回时自动调用
}

4.2 执行顺序(LIFO)

多个 defer 按**后进先出(栈)**的顺序执行:

func main() {
    defer fmt.Println("first defer")   // 第3个执行
    defer fmt.Println("second defer")  // 第2个执行
    defer fmt.Println("third defer")   // 第1个执行
    fmt.Println("main function")
}
// 输出:
// main function
// third defer
// second defer
// first defer

4.3 defer + 闭包

// ⚠️ defer 的参数在声明时就确定了
x := 10
defer fmt.Println(x)   // 打印 10,不是 20
x = 20

// 如果要延迟到执行时取值,用闭包
defer func() {
    fmt.Println(x)  // 打印 20(闭包捕获的是变量引用)
}()
x = 20

4.4 defer 的执行时机

func f() (result int) {
    defer func() {
        result++  // 修改了返回值!
    }()
    return 0
}
// f() 返回 1,不是 0
// 因为 return 先设置返回值,然后执行 defer

这个特性要谨慎使用。命名返回值 + defer 修改返回值在某些场景有用(如错误处理),但过于晦涩,建议在代码中加注释。

4.5 defer 的典型场景

// 1. 资源释放
file, _ := os.Open("data.txt")
defer file.Close()

conn, _ := net.Dial("tcp", "localhost:8080")
defer conn.Close()

// 2. 解锁
mu.Lock()
defer mu.Unlock()

// 3. 关闭数据库连接
rows, _ := db.Query("SELECT * FROM users")
defer rows.Close()

// 4. 计时器
start := time.Now()
defer func() {
    fmt.Printf("耗时: %v\n", time.Since(start))
}()

五、goto 与 break/continue

5.1 goto(不推荐)

// Go 保留了 goto,但几乎不使用
// 限制:不能跳过变量声明,不能跳入其他代码块
func main() {
    i := 0
loop:
    if i < 5 {
        fmt.Println(i)
        i++
        goto loop  // 能用,但不推荐
    }
}

5.2 break 和 continue

// break/continue 作用于最内层的 for 循环
for i := 0; i < 10; i++ {
    if i == 3 {
        continue  // 跳过本次迭代
    }
    if i == 7 {
        break  // 跳出循环
    }
    fmt.Println(i)  // 0 1 2 4 5 6
}

// 带标签的 break/continue:跳出外层循环
Outer:
    for i := 0; i < 5; i++ {
        for j := 0; j < 5; j++ {
            if i+j == 6 {
                break Outer  // 跳出外层循环
            }
        }
    }

六、本节要点速查

概念要点
if不需要括号,可带初始化语句,err != nil 是惯用模式
forGo 唯一的循环语句,覆盖 while/for-everything
range遍历切片/Map/字符串,值是副本(修改用索引)
switch默认不穿透,case 可多值,可无需条件
fallthrough强制穿透下一个 case,很少用
defer延迟执行,LIFO 顺序,参数声明时求值
defer + return命名返回值时 defer 可修改返回值
goto保留但不推荐
break + 标签可跳出外层循环

上一篇:03-复合类型 下一篇:05-错误处理