Go 流程控制

Go 语言提供了多种流程控制结构,用于控制程序的执行流程。主要包括:

  • 条件语句:if-else
  • 选择语句:switch-case
  • 循环语句:for
  • 跳转语句:
    • 无条件跳转:goto
    • 条件跳转:breakcontinue
  • 延迟语句:defer
  • 异常处理:panicrecover

一、if-else 条件语句

语法:

1
2
3
4
5
6
7
if 条件 1 {
分支 1
} else if 条件 2 {
分支 2
} else {
分支 else
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
age := 20

if age < 18 {
fmt.Println("未成年人")
} else if age >= 18 && age < 65 {
fmt.Println("成年人")
} else {
fmt.Println("老年人")
}
}

二、switch-case 选择语句

语法:

1
2
3
4
5
6
7
8
switch 表达式 {
case1:
分支 1
case2:
分支 2
default:
分支 default
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
day := 6

switch day {
case 1:
fmt.Println("周一")
case 2:
fmt.Println("周二")
case 3:
fmt.Println("周三")
case 4:
fmt.Println("周四")
case 5:
fmt.Println("周五")
case 6, 7: // case 可以同时匹配多个值
fmt.Println("周末")
default:
fmt.Println("未知")
}
}

当 case 使用关键字 fallthrough 开启穿透能力的时候,case 匹配成功后会继续执行下一个 case 的代码(fallthrough 只能穿一层),而不进行条件判断,直到遇到 break 或 switch 结束为止。

1
2
3
4
5
6
7
8
9
10
11
s := "hello"
switch {
case s == "hello":
fmt.Println("hello")
fallthrough
case s != "world":
fmt.Println("world")
}
// 输出:
// hello
// world

三、for 循环语句

Go 语言只有 for 关键字,没有 whiledo-while

语法:

1
2
3
for [condition | (init; condition; increment) | Range] {
循环体
}
  • 可以看到 for 后面可以接三种表达式:
    • 接一个条件表达式,类似 while 循环
    • 接三个表达式,类似传统的 for 循环
    • 接 range 关键字,遍历数组、切片、字符串、Map 等
  • 如果不接表达式,等价于无限循环

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
// 方式一:条件表达式
i := 0
for i < 5 {
fmt.Println(i)
i++
}

// 方式二:传统 for 循环
for j := 0; j < 5; j++ {
fmt.Println(j)
}

// 方式三:for range 循环
arr := []string{"a", "b", "c"}
for index, value := range arr {
fmt.Printf("索引 %d: 值 %s\n", index, value)
}
}

四、跳转语句

4.1 goto 无条件跳转语句

goto 语句用于无条件跳转到函数内的某个标签位置,标签由一个标识符和冒号 : 组成。
goto 可以跳转到同一函数内的任何位置,但不能跳转到其他函数。

语法:

1
2
3
4
goto 标签
...
...
标签: 表达式

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
goto flag
fmt.Println("B")
flag:
fmt.Println("A")
}

// 输出:
// A

4.2 break 跳转语句

break 语句用于跳出当前循环或 switch 语句块,结束当前循环或 switch 的执行。

示例:

1
2
3
4
5
6
7
8
9
10
11
for i := 0; i < 5; i++ {
if i == 3 {
break // 跳出循环
}
fmt.Println(i)
}

// 输出:
// 0
// 1
// 2

4.3 continue 跳转语句

continue 语句用于跳过当前循环的剩余代码,直接进入下一次循环的判断。

示例:

1
2
3
4
5
6
7
8
9
10
11
for i := 0; i < 5; i++ {
if i == 3 {
continue // 跳过当前循环
}
fmt.Println(i)
}
// 输出:
// 0
// 1
// 2
// 4 被跳过

五、defer 延迟语句

defer 语句用于注册一个函数调用,这个调用会在包含它的函数执行结束后被自动执行,无论函数是正常返回还是因为 panic 而终止。

语法:

1
defer 函数调用

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
fmt.Println("开始执行 main 函数")

defer fmt.Println("这是 defer 语句 1") // 延迟执行
defer fmt.Println("这是 defer 语句 2") // 延迟执行

fmt.Println("main 函数即将结束")
// defer 语句在 main 函数 return 之前执行
}

// 输出顺序:
// 1. 开始执行 main 函数
// 2. main 函数即将结束
// 3. 这是 defer 语句 2 <-- 注意顺序是 LIFO
// 4. 这是 defer 语句 1

defer 核心用途

  • 文件操作:确保文件句柄被关闭:defer file.Close()
  • 数据库连接:确保数据库连接被释放:defer db.Close()
  • 锁机制:确保锁被释放:defer mutex.Unlock()

注意

  • defer 语句的参数是在 defer 语句被定义时立即求值的,而不是在它被执行时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func main() {
    i := 1

    // defer 语句被定义时,i 的值是 1,所以参数 i 立即被求值并固定为 1。
    defer fmt.Println("defer 结果:", i)
    i++ // i 的值现在是 2,但这不影响 defer 语句的参数 i。

    fmt.Println("main 结束前 i:", i)

    // 输出顺序:
    // 1. main 结束前 i: 2
    // 2. defer 结果: 1 <-- 打印的是 i 声明时的值 1
    }

六、panic 和 recover 异常处理

6.1 panic 抛出异常

panic 是一个内置函数,用于中断正常的流程控制

  • 当函数 func 中执行 panic 时,func 之后的代码将不再执行
  • panic 会沿着调用栈向上传播,执行当前 Goroutine 中所有已注册的 defer 函数
  • 如果调用栈上的所有 defer 函数都执行完毕,但 panic 仍未被捕获,程序将打印堆栈信息并崩溃退出。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
defer fmt.Println("这是 main 函数的 defer") // 会在 panic 发生后执行
fmt.Println("开始执行")

// 手动触发 panic
panic("发生了一个严重错误!")

fmt.Println("这行代码不会被执行")
}
// 输出:
// 开始执行
// 这是 main 函数的 defer
// panic: 发生了一个严重错误!
// ... (堆栈信息)

6.2 recover 捕获异常

recover 是一个内置函数,必须在 defer 函数中调用,才能捕获到 panic 抛出的错误,并阻止程序崩溃。

  • 有效调用: 在 defer 函数内直接调用 recover()
  • 作用: 如果一个 panic 正在进行,recover 会捕获到 panic 的参数(即 panic() 函数的参数),并终止 panic 过程,让程序恢复正常执行流程
  • 恢复位置:捕获 panic 后,程序不会回到 panic 发生的点,而是继续执行 defer 之后的代码

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

func safeDivide(a, b int) {
// 必须在 defer 匿名函数中调用 recover
defer func() {
if r := recover(); r != nil {
// r 就是 panic() 传递的参数
fmt.Printf("程序恢复!捕获到 panic 错误: %v\n", r)
}
}()

fmt.Println("尝试进行除法计算...")

// 故意制造一个会导致 panic 的操作
z := a / b // 如果 b=0,会触发 runtime panic: integer divide by zero

fmt.Printf("%d / %d = %d\n", a, b, z)
}

func main() {
safeDivide(10, 2) // 正常执行
fmt.Println("--------------------")
safeDivide(10, 0) // 发生 panic,但被 recover 捕获并恢复

// 由于 panic 被捕获,程序可以继续执行后续代码
fmt.Println("--------------------")
fmt.Println("main 函数继续执行,程序没有崩溃。")
}

// 输出:
// 尝试进行除法计算...
// 10 / 2 = 5
// --------------------
// 尝试进行除法计算...
// 程序恢复!捕获到 panic 错误: runtime error: integer divide by zero
// --------------------
// main 函数继续执行,程序没有崩溃。