404 Not Found

404 Not Found


nginx

控制流

控制流

想象你是一位厨师在做菜:如果锅热了就放油(if),根据食材选择不同的烹饪方式(switch),反复翻炒直到熟透(for),出锅前别忘了关火(defer)。程序也是如此——控制流决定了代码的执行路径,让计算机学会"做决定"和"重复做事"。


1. 核心概念

Go 语言的控制流语句简洁而强大,主要包含以下几类:

1.1 if / else 条件判断

Go 的 if 语句有一个独特之处:可以在条件前添加一个初始化语句,用分号 ; 隔开。变量的作用域仅限于 if/else 块内。

GO
// 基本形式
if 条件 {
    // 条件为真时执行
} else if 其他条件 {
    // 其他条件为真时执行
} else {
    // 以上都不满足时执行
}

// 带初始化语句的形式
if 初始化语句; 条件 {
    // 初始化的变量仅在 if/else 块内可见
}

1.2 switch 选择语句

Go 的 switch 与其他语言有两大关键区别:

GO
switch 变量 {
case 值1:
    // 处理值1
case 值2, 值3: // 多个值用逗号分隔
    // 处理值2或值3
default:
    // 默认处理
}

1.3 for 循环

for 是 Go 语言中唯一的循环结构。它取代了其他语言中的 whiledo-while 等循环。

形式 语法 用途
传统 for for init; cond; post {} 已知循环次数
while 风格 for cond {} 条件循环
无限循环 for {} 持续运行
range 遍历 for i, v := range 集合 {} 遍历集合

1.4 break 和 continue

1.5 defer 延迟执行

defer 语句会将函数调用推迟到外层函数返回之后执行。多个 defer后进先出(LIFO)的栈顺序执行。

GO
func example() {
    defer fmt.Println("第一个注册,最后执行")
    defer fmt.Println("第二个注册,倒数第二执行")
    fmt.Println("正常执行")
}
// 输出顺序:正常执行 → 第二个注册 → 第一个注册

2. 基本语法/用法

if / else 用法

GO
// 标准 if/else
score := 85
if score >= 90 {
    fmt.Println("优秀")
} else if score >= 80 {
    fmt.Println("良好")
} else if score >= 60 {
    fmt.Println("及格")
} else {
    fmt.Println("不及格")
}
💡 提示:Go 中的 if 条件不需要括号,但花括号 {} 是必须的,且 else 必须与 if 的右花括号在同一行。这是 Go 的语法规则,不遵守会导致编译错误。

GO
// 带初始化语句的 if
// err 的作用域仅在 if/else 块内,出了这个块就无法访问
if err := doSomething(); err != nil {
    fmt.Println("出错了:", err)
}
💡 提示:带初始化语句的 if 在 Go 中非常常见,特别是处理错误时。它能让变量的作用域最小化,避免污染外层作用域。

switch 用法

GO
// 基本 switch
day := "周三"
switch day {
case "周一":
    fmt.Println("新的一周开始了")
case "周二", "周三", "周四":
    fmt.Println("工作日")
case "周五":
    fmt.Println("快到周末了")
case "周六", "周日":
    fmt.Println("周末愉快")
default:
    fmt.Println("无效的日期")
}
💡 提示:Go 的 switch 不需要在每个 case 后写 break。如果确实需要"贯穿"到下一个 case,可以使用 fallthrough 关键字,但这种用法很少见。

GO
// 无条件 switch(替代长 if-else 链)
score := 85
switch {
case score >= 90:
    fmt.Println("优秀")
case score >= 80:
    fmt.Println("良好")
case score >= 60:
    fmt.Println("及格")
default:
    fmt.Println("不及格")
}
💡 提示:当 switch 后不跟变量时,它等价于 switch true,可以用来替代冗长的 if-else 链,代码更清晰。

for 循环用法

GO
// 传统 for 循环
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// while 风格
count := 0
for count < 5 {
    fmt.Println(count)
    count++
}

// 无限循环(需配合 break 使用)
for {
    fmt.Println("按 Ctrl+C 退出")
    break // 这里用 break 避免真正的无限循环
}

defer 用法

GO
func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    defer file.Close() // 确保函数结束时关闭文件
    // ... 读取文件内容 ...
}
💡 提示:defer 的参数会在 defer 语句出现时立即求值,而不是在函数执行时求值。注意这个细节,避免意外行为。


示例 1:基础用法(难度⭐)

本示例演示 if/elseswitch 的基本使用。

GO
package main

import "fmt"

func main() {
    // ===== if/else 示例:判断成绩等级 =====
    score := 78

    // 使用带初始化语句的 if
    // rank 变量仅在 if/else 块内有效
    if rank := score / 10; rank >= 9 {
        fmt.Printf("成绩 %d 分,等级:优秀\n", score)
    } else if rank >= 8 {
        fmt.Printf("成绩 %d 分,等级:良好\n", score)
    } else if rank >= 6 {
        fmt.Printf("成绩 %d 分,等级:及格\n", score)
    } else {
        fmt.Printf("成绩 %d 分,等级:不及格\n", score)
    }

    // ===== switch 示例:根据月份判断季节 =====
    month := 8

    switch month {
    case 3, 4, 5:
        fmt.Printf("%d 月是春季\n", month)
    case 6, 7, 8:
        fmt.Printf("%d 月是夏季\n", month)
    case 9, 10, 11:
        fmt.Printf("%d 月是秋季\n", month)
    case 12, 1, 2:
        fmt.Printf("%d 月是冬季\n", month)
    default:
        fmt.Printf("%d 不是有效的月份\n", month)
    }

    // ===== 无条件 switch:替代 if-else 链 =====
    hour := 14
    switch {
    case hour < 6:
        fmt.Println("凌晨时分")
    case hour < 12:
        fmt.Println("上午时光")
    case hour < 18:
        fmt.Println("下午时光")
    default:
        fmt.Println("晚上时光")
    }
}
▶ 试一试

输出结果:

TEXT
成绩 78 分,等级:及格
8 月是夏季
下午时光

代码要点:


示例 2:进阶用法(难度⭐⭐)

本示例演示 for 循环的各种形式以及 break / continue 的使用。

GO
package main

import "fmt"

func main() {
    // ===== 传统 for 循环:计算 1 到 100 的和 =====
    sum := 0
    for i := 1; i <= 100; i++ {
        sum += i
    }
    fmt.Printf("1 到 100 的和为: %d\n", sum)

    // ===== range 遍历切片 =====
    fruits := []string{"苹果", "香蕉", "橘子", "葡萄"}
    for index, fruit := range fruits {
        fmt.Printf("索引 %d: %s\n", index, fruit)
    }

    // ===== range 遍历字符串(按 rune) =====
    text := "Go语言"
    for i, ch := range text {
        fmt.Printf("位置 %d: %c (Unicode: %U)\n", i, ch, ch)
    }

    // ===== break:找到第一个能被 7 整除的数 =====
    for i := 1; i <= 100; i++ {
        if i%7 == 0 {
            fmt.Printf("第一个能被 7 整除的数: %d\n", i)
            break // 找到后立即退出循环
        }
    }

    // ===== continue:打印 1-20 中所有奇数 =====
    fmt.Print("1-20 的奇数: ")
    for i := 1; i <= 20; i++ {
        if i%2 == 0 {
            continue // 跳过偶数,进入下一次循环
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println()

    // ===== 带标签的 break:跳出外层循环 =====
    fmt.Println("在二维数组中查找数字 5:")
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

found: // 标签名称
    for row, cols := range matrix {
        for col, val := range cols {
            if val == 5 {
                fmt.Printf("找到了!位置: 第 %d 行, 第 %d 列\n", row, col)
                break found // 跳出外层循环
            }
        }
    }
}
▶ 试一试

输出结果:

TEXT
1 到 100 的和为: 5050
索引 0: 苹果
索引 1: 香蕉
索引 2: 橘子
索引 3: 葡萄
位置 0: G (Unicode: U+0047)
位置 1: o (Unicode: U+006F)
位置 2: 语 (Unicode: U+8BED)
位置 5: 言 (Unicode: U+8A00)
第一个能被 7 整除的数: 7
1-20 的奇数: 1 3 5 7 9 11 13 15 17 19
在二维数组中查找数字 5:
找到了!位置: 第 1 行, 第 1 列

代码要点:


示例 3:综合应用(难度⭐⭐⭐)

本示例综合运用所有控制流语句,实现一个简单的学生成绩管理系统。

GO
package main

import "fmt"

// Student 学生结构体
type Student struct {
    Name   string
    Scores []int
}

// GetAverage 计算平均分
func (s Student) GetAverage() float64 {
    if len(s.Scores) == 0 {
        return 0
    }
    total := 0
    for _, score := range s.Scores {
        total += score
    }
    return float64(total) / float64(len(s.Scores))
}

// GetGrade 根据平均分获取等级
func (s Student) GetGrade() string {
    avg := s.GetAverage()
    switch {
    case avg >= 90:
        return "A (优秀)"
    case avg >= 80:
        return "B (良好)"
    case avg >= 70:
        return "C (中等)"
    case avg >= 60:
        return "D (及格)"
    default:
        return "F (不及格)"
    }
}

// PrintReport 打印成绩报告
func PrintReport(students []Student) {
    fmt.Println("========================================")
    fmt.Println("         学 生 成 绩 报 告")
    fmt.Println("========================================")

    // 使用 defer 确保报告结尾在所有学生数据打印完之后输出
    defer fmt.Println("========================================")
    defer fmt.Println("         报 告 生成完毕")

    for i, student := range students {
        // 使用 defer 演示 LIFO 顺序
        defer func(name string, idx int) {
            fmt.Printf("[清理] 完成 %s 的报告处理\n", name)
        }(student.Name, i)

        // 打印学生信息
        fmt.Printf("\n学生 %d: %s\n", i+1, student.Name)
        fmt.Printf("  各科成绩: %v\n", student.Scores)
        fmt.Printf("  平均分:   %.1f\n", student.GetAverage())
        fmt.Printf("  等级:     %s\n", student.GetGrade())

        // 分析每科成绩
        subjects := []string{"语文", "数学", "英语"}
        for j, score := range student.Scores {
            // 确保索引不越界
            subject := "未知科目"
            if j < len(subjects) {
                subject = subjects[j]
            }

            // 对每科成绩进行判断
            if score < 60 {
                fmt.Printf("  ⚠ %s(%d分) 不及格,需要补考\n", subject, score)
            }
        }
    }
}

// FindTopStudent 查找成绩最好的学生
func FindTopStudent(students []Student) (string, float64) {
    if len(students) == 0 {
        return "", 0
    }

    topName := students[0].Name
    topAvg := students[0].GetAverage()

    for _, s := range students[1:] {
        if avg := s.GetAverage(); avg > topAvg {
            topName = s.Name
            topAvg = avg
        }
    }

    return topName, topAvg
}

func main() {
    // 创建学生数据
    students := []Student{
        {"张三", []int{85, 92, 78}},
        {"李四", []int{90, 95, 88}},
        {"王五", []int{72, 58, 65}},
        {"赵六", []int{95, 98, 92}},
    }

    // 打印成绩报告
    PrintReport(students)

    // 查找最优学生
    topName, topAvg := FindTopStudent(students)
    fmt.Printf("\n🏆 最优学生: %s (平均分: %.1f)\n", topName, topAvg)
}
▶ 试一试

输出结果:

TEXT
========================================
         学 生 成 绩 报 告
========================================

学生 1: 张三
  各科成绩: [85 92 78]
  平均分:   85.0
  等级:     B (良好)

学生 2: 李四
  各科成绩: [90 95 88]
  平均分:   91.0
  等级:     A (优秀)

学生 3: 王五
  各科成绩: [72 58 65]
  平均分:   65.0
  等级:     D (及格)
  ⚠ 数学(58分) 不及格,需要补考

学生 4: 赵六
  各科成绩: [95 98 92]
  平均分:   95.0
  等级:     A (优秀)
[清理] 完成 赵六 的报告处理
[清理] 完成 王五 的报告处理
[清理] 完成 李四 的报告处理
[清理] 完成 张三 的报告处理
========================================
         报 告 生成完毕
========================================

🏆 最优学生: 赵六 (平均分: 95.0)

代码要点:


3. 常见应用场景

场景 1:错误处理链(if + 初始化语句)

Go 语言中,if 的初始化语句最常见的用途是处理错误:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 解析数字并处理错误
    inputs := []string{"42", "abc", "100", "xyz", "0"}

    for _, input := range inputs {
        // 初始化语句中进行类型转换,条件中判断是否出错
        if num, err := strconv.Atoi(input); err != nil {
            fmt.Printf("❌ 无法解析 %q: %v\n", input, err)
        } else {
            fmt.Printf("✅ 解析成功: %q → %d\n", input, num)
        }
    }
}

场景 2:遍历 Map 并条件筛选(for + switch)

GO
package main

import "fmt"

func main() {
    // 学生成绩表
    students := map[string]int{
        "张三": 85,
        "李四": 92,
        "王五": 58,
        "赵六": 76,
        "钱七": 45,
    }

    // 统计各等级人数
    excellent, good, pass, fail := 0, 0, 0, 0

    for name, score := range students {
        switch {
        case score >= 90:
            excellent++
            fmt.Printf("⭐ %s: %d (优秀)\n", name, score)
        case score >= 80:
            good++
            fmt.Printf("👍 %s: %d (良好)\n", name, score)
        case score >= 60:
            pass++
            fmt.Printf("✅ %s: %d (及格)\n", name, score)
        default:
            fail++
            fmt.Printf("❌ %s: %d (不及格)\n", name, score)
        }
    }

    fmt.Printf("\n统计: 优秀 %d 人, 良好 %d 人, 及格 %d 人, 不及格 %d 人\n",
        excellent, good, pass, fail)
}

❓ 常见问题

Q1:为什么 Go 的 switch 不需要 break?

Go 语言设计者认为 C 语言中 switch 的 fallthrough(贯穿)行为是导致 bug 的常见来源。Go 的 switch 默认每个 case 执行完就自动退出,避免了忘记写 break 导致的意外贯穿。如果确实需要贯穿,可以显式使用 fallthrough 关键字。

GO
// fallthrough 的用法(少见)
x := 1
switch x {
case 1:
    fmt.Println("一")
    fallthrough // 继续执行下一个 case 的代码
case 2:
    fmt.Println("二") // 会被执行
default:
    fmt.Println("其他")
}
// 输出: 一 \n 二

Q2:for range 遍历时变量是复制的吗?

是的。for range 中的值变量是元素的副本,修改它不会影响原集合。如果需要修改原集合中的元素,应通过索引访问。

GO
nums := []int{1, 2, 3, 4, 5}

// ❌ 错误:修改的是副本
for _, n := range nums {
    n *= 2 // 不会影响 nums
}

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

Q3:defer 的参数什么时候求值?

defer 的参数在 defer 语句出现时立即求值,而不是在函数返回时求值。

GO
func main() {
    x := 10
    defer fmt.Println("defer 中的 x:", x) // x 在此处求值为 10
    x = 20
    fmt.Println("正常执行的 x:", x)
}
// 输出:
// 正常执行的 x: 20
// defer 中的 x: 10  (不是 20!)

Q4:如何在循环中使用 goroutine 时正确捕获循环变量?

在 Go 1.22 之前,for 循环变量在所有迭代中共享同一个地址,容易在 goroutine 中产生问题。Go 1.22+ 已修复此行为,每次迭代都有独立的变量。如果使用旧版本,需要将变量作为参数传入:

GO
// Go 1.22+ 直接使用即可
for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i) // 每个 goroutine 拿到独立的 i
    }()
}

// 旧版本需要显式传参
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n) // n 是 i 的副本
    }(i)
}

📖 小节


📝 作业

练习 1(⭐)

编写一个程序,输入一个年份,判断是否为闰年。规则:

GO
package main

import "fmt"

func main() {
    year := 2024 // 可以修改这个值测试

    // 在这里编写你的代码
    // 提示:使用 if init; cond 形式
}

练习 2(⭐⭐)

编写一个程序,使用 for 循环打印九九乘法表。格式要求:

GO
package main

import "fmt"

func main() {
    // 在这里编写你的代码
    // 提示:使用两层嵌套 for 循环
    // 外层循环 i 从 1 到 9
    // 内层循环 j 从 1 到 i
}

练习 3(⭐⭐⭐)

编写一个程序,实现一个简单的猜数字游戏:

  1. 使用循环让用户反复猜测,直到猜对为止
  2. 每次猜测后给出"太大"或"太小"的提示
  3. 使用 defer 记录游戏开始和结束的时间
  4. 使用 switch 根据猜测次数给出不同评价(1-3 次:天才,4-6 次:不错,7+ 次:继续努力)
GO
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // 使用 defer 记录结束时间
    start := time.Now()
    defer func() {
        fmt.Printf("\n游戏用时: %v\n", time.Since(start))
    }()

    // 生成 1-100 的随机数
    target := rand.Intn(100) + 1
    attempts := 0

    fmt.Println("=== 猜数字游戏 ===")
    fmt.Println("我想了一个 1-100 之间的数字,来猜猜看!")

    // 在这里编写你的代码
    // 提示:
    // 1. 使用 for 循环持续游戏
    // 2. 使用 fmt.Scan 读取用户输入
    // 3. 使用 switch 判断猜测结果和评价等级
    // 4. 猜对时使用 break 退出循环
}

下一课

下一课:函数 → 学习 Go 语言的函数定义、多返回值、可变参数、匿名函数和闭包等核心概念。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏