控制结构决定了程序的执行路径。Go语言的设计者推崇“少即是多”的理念,因此只保留了最必要的控制语句:条件判断(if/else)、循环(for)、分支选择(switch)、延迟执行(defer)以及极少使用的跳转(goto)。本章将逐一深入讲解,并通过实例展示Go风格的代码组织方式。
Go的if语句与C语言类似,但取消了条件表达式周围的括号,并且可以在条件判断之前执行一个简单的初始化语句。
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("优秀")
} else if score >= 70 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
}
在if关键字和条件表达式之间可以添加一个简短的语句(通常是变量声明),该变量的作用域仅限于if-else块内部。
if err := processFile(); err != nil {
fmt.Println("错误:", err)
return
} else {
// 这里也可以访问 err
fmt.Println("处理成功")
}
// 在此处无法访问 err
这种写法非常适用于需要调用函数并检查返回错误的场景,能让错误处理的代码更紧凑。
Go语言只有for一种循环关键字,但可以以多种形式使用,足以替代其他语言的while和do-while。
for i := 0; i < 5; i++ {
fmt.Println("i =", i)
}
省略初始化语句和后置语句,只保留条件,即成为while循环。
sum := 1
for sum < 100 {
sum += sum
}
fmt.Println(sum) // 128
省略所有三个部分,形成无限循环,通常与break配合使用。
count := 0
for {
count++
if count >= 3 {
break
}
fmt.Println("loop")
}
range在迭代中返回索引(或键)和值。
// 遍历切片
nums := []int{10, 20, 30}
for idx, val := range nums {
fmt.Printf("索引:%d 值:%d\n", idx, val)
}
// 遍历字符串(按rune,而非字节)
name := "Go语言"
for i, ch := range name {
fmt.Printf("%d: %c\n", i, ch) // 注意索引是字节位置,i可能不连续
}
// 遍历map
ages := map[string]int{"Alice": 25, "Bob": 30}
for k, v := range ages {
fmt.Printf("%s -> %d\n", k, v)
}
// 如果只关心值,可用下划线占位索引
for _, val := range nums {
fmt.Println(val)
}
break跳出当前循环,continue跳过本次迭代。二者都可以配合标签(label)跳出多层循环。
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 5; j++ {
if j == 2 {
break outer // 跳出外层循环
}
fmt.Printf("i=%d,j=%d\n", i, j)
}
}
// 输出:i=0,j=0 i=0,j=1
Go的switch比C/C++更灵活:每个case默认自带break(不会贯穿到下一个case),且case表达式可以是任意类型(不限于整数),甚至可以是布尔条件。
day := "Saturday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("工作日")
case "Saturday", "Sunday":
fmt.Println("周末")
default:
fmt.Println("无效的日期")
}
如果switch后面没有表达式,则每个case被视为布尔表达式。
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("D")
}
类似于if,可以在switch前加一个简短语句。
switch x := 2; x {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
default:
fmt.Println("other")
}
如果希望执行完一个case后强制执行下一个case(即使表达式不匹配),可以使用fallthrough。
n := 1
switch n {
case 1:
fmt.Println("1")
fallthrough
case 2:
fmt.Println("2")
case 3:
fmt.Println("3")
}
// 输出: 1 \n 2
fallthrough,它违背了Go默认的安全设计,容易引入逻辑错误。
defer语句会将一个函数调用推迟到外层函数返回之前执行,常用于资源释放(如关闭文件、解锁互斥锁)。多个defer以后进先出(LIFO)的顺序执行。
package main
import "fmt"
func main() {
defer fmt.Println("world") // 第二个执行
defer fmt.Println("hello") // 第一个执行
fmt.Println("start")
// 输出顺序: start hello world
}
func readFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close() // 确保函数退出时关闭文件
// 处理文件...
return nil
}
defer的参数在定义时即计算,而非调用时。如果defer的函数有返回值,它会被忽略。常用于配对操作(open/close, lock/unlock)。
goto可以跳转到同一函数内定义的标签。过度使用会导致代码难以理解,但有些场景(如跳出多层循环或统一错误处理)可以使代码更清晰。
func main() {
i := 0
for {
i++
if i == 3 {
goto Exit
}
fmt.Println(i)
}
Exit:
fmt.Println("跳转到此处,循环结束")
}
goto,除非能显著提升代码清晰度(例如在错误处理中跳转到统一的清理代码块)。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
target := rand.Intn(100) + 1 // 1~100
var guess int
attempts := 0
fmt.Println("猜数字游戏,范围1-100")
for {
fmt.Print("请输入你的猜测: ")
fmt.Scan(&guess)
attempts++
if guess < target {
fmt.Println("太小了,继续猜")
} else if guess > target {
fmt.Println("太大了,继续猜")
} else {
fmt.Printf("恭喜!用了%d次猜对了!\n", attempts)
break
}
}
defer fmt.Println("游戏结束,感谢参与!")
}
运行示例:
猜数字游戏,范围1-100
请输入你的猜测: 50
太大了,继续猜
...
恭喜!用了6次猜对了!
游戏结束,感谢参与!
for和if完成大多数流程控制。下一章我们将学习Go语言中强大的函数特性——多返回值、命名返回值、可变参数、闭包等。