第 3 章:流程控制与函数

jerry北京市2026年4月29日Go 5 次阅读 约 13 分钟
第 3 章:流程控制与函数

掌握 Go 的条件判断、循环、switch 语句,以及函数的定义、多返回值、defer 和闭包。


3.1 条件判断

if 语句

x := 10

if x > 0 {
    fmt.Println("正数")
} else if x < 0 {
    fmt.Println("负数")
} else {
    fmt.Println("零")
}

Go 的 if 有一个独特特性——可以在条件前加一个初始化语句:

// err 的作用域仅限于 if-else 块内
if err := doSomething(); err != nil {
    fmt.Println("出错了:", err)
    return
}

这种写法在 Go 中非常常见,特别是错误处理场景。

3.2 循环

Go 只有 for 一种循环关键字,但它能覆盖所有循环场景:

// 标准 for 循环
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// 类似 while
n := 1
for n < 100 {
    n *= 2
}

// 无限循环
for {
    // 用 break 退出
    break
}

// range 遍历
nums := []int{1, 2, 3}
for index, value := range nums {
    fmt.Printf("索引:%d 值:%d\n", index, value)
}

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

// 遍历 map
m := map[string]int{"a": 1, "b": 2}
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

// 遍历字符串(按 rune)
for i, ch := range "你好" {
    fmt.Printf("%d: %c\n", i, ch)
}

break 和 continue 配合标签

outer:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if i == 1 && j == 1 {
                break outer  // 跳出外层循环
            }
            fmt.Printf("i=%d j=%d\n", i, j)
        }
    }

3.3 switch 语句

Go 的 switch 不需要 break,匹配到一个 case 后自动终止:

day := "Monday"
switch day {
case "Monday":
    fmt.Println("周一")
case "Friday":
    fmt.Println("周五")
default:
    fmt.Println("其他")
}

// 多值匹配
switch day {
case "Saturday", "Sunday":
    fmt.Println("周末")
default:
    fmt.Println("工作日")
}

// 无条件 switch(替代 if-else 链)
score := 85
switch {
case score >= 90:
    fmt.Println("优秀")
case score >= 80:
    fmt.Println("良好")
case score >= 60:
    fmt.Println("及格")
default:
    fmt.Println("不及格")
}

如果需要穿透到下一个 case,使用 fallthrough

switch 1 {
case 1:
    fmt.Println("1")
    fallthrough
case 2:
    fmt.Println("2")  // 也会执行
}

3.4 函数定义

// 基本函数
func add(a int, b int) int {
    return a + b
}

// 参数类型相同可以简写
func add(a, b int) int {
    return a + b
}

// 多返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为零")
    }
    return a / b, nil
}

result, err := divide(10, 3)
if err != nil {
    fmt.Println(err)
}

命名返回值

func swap(a, b int) (x, y int) {
    x = b
    y = a
    return  // 裸 return,自动返回 x 和 y
}

命名返回值在短函数中可以提高可读性,但在长函数中建议显式 return。

可变参数

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

fmt.Println(sum(1, 2, 3))      // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15

// 展开切片传入
nums := []int{1, 2, 3}
fmt.Println(sum(nums...))       // 6

3.5 函数是一等公民

Go 中函数可以赋值给变量、作为参数传递、作为返回值:

// 函数赋值给变量
add := func(a, b int) int {
    return a + b
}
fmt.Println(add(1, 2))

// 函数作为参数
func apply(a, b int, op func(int, int) int) int {
    return op(a, b)
}
result := apply(3, 4, func(a, b int) int {
    return a * b
})
fmt.Println(result)  // 12

3.6 闭包(Closure)

闭包是引用了外部变量的函数:

func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c := counter()
fmt.Println(c())  // 1
fmt.Println(c())  // 2
fmt.Println(c())  // 3

闭包的关键点:内部函数持有外部变量的引用(不是拷贝),所以每次调用都能修改同一个变量。

闭包陷阱

// 经典陷阱:循环变量捕获
funcs := make([]func(), 3)
for i := 0; i < 3; i++ {
    funcs[i] = func() {
        fmt.Println(i)  // 捕获的是变量 i 的引用
    }
}
funcs[0]()  // 3(不是 0)
funcs[1]()  // 3
funcs[2]()  // 3

// 解决方案:通过参数传值
for i := 0; i < 3; i++ {
    i := i  // 创建新的局部变量(Go 1.22+ 循环变量已修复此问题)
    funcs[i] = func() {
        fmt.Println(i)
    }
}

3.7 defer

defer 延迟执行函数调用,在当前函数返回前执行。多个 defer 按 LIFO(后进先出)顺序执行:

func main() {
    fmt.Println("开始")
    defer fmt.Println("延迟 1")
    defer fmt.Println("延迟 2")
    defer fmt.Println("延迟 3")
    fmt.Println("结束")
}
// 输出:
// 开始
// 结束
// 延迟 3
// 延迟 2
// 延迟 1

常见用途——资源清理:

func readFile(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()  // 确保函数返回前关闭文件

    // 读取文件内容...
    return nil
}

defer 的求值时机

func main() {
    x := 10
    defer fmt.Println(x)  // x 在 defer 时就被求值了,值为 10
    x = 20
}
// 输出:10(不是 20)

3.8 init 函数

每个包可以有一个或多个 init 函数,在 main 函数之前自动执行:

var config map[string]string

func init() {
    config = make(map[string]string)
    config["env"] = "production"
    fmt.Println("初始化完成")
}

func main() {
    fmt.Println(config["env"])
}
// 输出:
// 初始化完成
// production

执行顺序:全局变量初始化 → init()main()

3.9 面试要点

  1. Go 为什么只有 for 循环?

    • 简化语言设计,一个关键字覆盖所有循环场景
  2. defer 的执行顺序和求值时机?

    • LIFO 顺序执行,参数在 defer 语句处立即求值
  3. 闭包捕获变量是值还是引用?

    • 引用。闭包持有外部变量的引用,修改会影响原变量
  4. switch 和其他语言有什么不同?

    • 不需要 break,自动终止
    • 支持无条件 switch
    • 需要穿透时用 fallthrough

练习

  1. 编写一个函数,接收一个整数切片,返回其中的最大值和最小值
  2. 使用闭包实现一个斐波那契数列生成器
  3. 编写一个函数,使用 defer 记录函数的执行时间
  4. 使用无条件 switch 实现一个简单的成绩等级判断

← 上一章:基本数据类型与变量 | 下一章:复合数据类型 →

评论

登录 后发表评论

暂无评论