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 是惯用模式 |
for | Go 唯一的循环语句,覆盖 while/for-everything |
range | 遍历切片/Map/字符串,值是副本(修改用索引) |
switch | 默认不穿透,case 可多值,可无需条件 |
fallthrough | 强制穿透下一个 case,很少用 |
defer | 延迟执行,LIFO 顺序,参数声明时求值 |
| defer + return | 命名返回值时 defer 可修改返回值 |
goto | 保留但不推荐 |
break + 标签 | 可跳出外层循环 |
豫公网安备41108202000982号