第04章 - 流程控制与函数
本章讲解 Go 的流程控制语句(条件、循环、分支)以及函数的定义与高级用法,包括多返回值、可变参数、闭包和 defer 机制。
4.1 条件语句 if
Go 的 if 语句不需要括号包裹条件,但花括号是必须的:
score := 85
if score >= 90 {
fmt.Println("优秀")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
4.1.1 带初始化语句的 if
if 可以在条件前包含一个初始化语句,该语句声明的变量作用域仅限于 if-else 块内,这是 Go 非常地道的写法:
if value, err := strconv.Atoi("123"); err == nil {
fmt.Println("转换成功:", value)
} else {
fmt.Println("转换失败:", err)
}
// value 和 err 在这里已不可见
这种模式将变量作用域限制在最小范围,是处理错误的标准方式。
4.2 循环语句 for
Go 只有一种循环关键字 for,但它能表达所有循环形式。
4.2.1 经典三段式
for i := 0; i < 10; i++ {
fmt.Println(i)
}
4.2.2 类 while 循环
省略初始化和后置语句,for 就相当于 while:
n := 1
for n < 100 {
n *= 2
}
4.2.3 无限循环
for {
// 永久循环,通常配合 break 或 return 退出
if done {
break
}
}
4.2.4 range 遍历
range 用于遍历数组、切片、字符串、map 和 channel:
nums := []int{10, 20, 30}
for index, value := range nums {
fmt.Printf("索引 %d = %d\n", index, value)
}
// 只要索引
for index := range nums {
fmt.Println(index)
}
// 只要值,用下划线忽略索引
for _, value := range nums {
fmt.Println(value)
}
// 遍历 map
m := map[string]int{"a": 1, "b": 2}
for key, val := range m {
fmt.Println(key, val)
}
4.3 跳转语句
break:跳出当前循环或switch。continue:跳过本次循环,进入下一次迭代。goto:跳转到指定标签(极少使用)。- 标签(label):配合
break/continue可跳出多层嵌套循环:
outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i*j > 2 {
break outer // 直接跳出外层循环
}
}
}
4.4 分支语句 switch
Go 的 switch 比 C 更强大,且默认不需要 break——每个 case 执行完自动跳出。
switch day {
case "Sat", "Sun": // 一个 case 可匹配多个值
fmt.Println("周末")
default:
fmt.Println("工作日")
}
4.4.1 表达式 switch
switch 后可以不跟变量,此时相当于 if-else 链:
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
default:
fmt.Println("C")
}
4.4.2 fallthrough
若希望继续执行下一个 case(C 语言的默认行为),可使用 fallthrough:
switch n {
case 1:
fmt.Println("one")
fallthrough
case 2:
fmt.Println("two") // 当 n==1 时也会执行
}
4.4.3 类型 switch
用于判断接口的动态类型(详见第 6 章):
switch v := i.(type) {
case int:
fmt.Println("整数:", v)
case string:
fmt.Println("字符串:", v)
default:
fmt.Println("未知类型")
}
4.5 函数定义
函数使用 func 关键字定义,基本语法为 func 函数名(参数列表) 返回值类型 { 函数体 }:
func add(a int, b int) int {
return a + b
}
// 相同类型的参数可合并书写
func add(a, b int) int {
return a + b
}
4.5.1 多返回值
Go 函数可以返回多个值,这是其一大特色,广泛用于”返回结果 + 错误”的模式:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
result, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("结果:", result)
4.5.2 命名返回值
返回值可以命名,相当于在函数开头声明了这些变量,return 时可不带参数(裸返回):
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // 裸返回,返回 x 和 y
}
命名返回值能提升可读性,但裸返回在长函数中会降低可读性,应谨慎使用。
4.5.3 可变参数
参数类型前加 ... 表示可变参数,函数内部将其视为切片:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15
// 将切片展开传入
s := []int{1, 2, 3}
sum(s...)
4.6 函数是一等公民
在 Go 中,函数是一等公民(first-class citizen),可以赋值给变量、作为参数传递、作为返回值返回。
// 函数类型变量
var op func(int, int) int
op = add
fmt.Println(op(3, 4))
// 函数作为参数(回调)
func apply(a, b int, fn func(int, int) int) int {
return fn(a, b)
}
4.6.1 匿名函数与闭包
匿名函数可以捕获外部变量,形成闭包:
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
每次调用 counter() 都会创建一个独立的 count 变量,闭包持有对它的引用。
4.7 defer 延迟执行
defer 用于延迟执行一个函数调用,被延迟的调用会在外层函数返回前执行,常用于资源释放、解锁、关闭文件等场景:
func readFile(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // 函数返回前自动关闭文件
// ... 处理文件 ...
return nil
}
4.7.1 defer 的执行顺序
多个 defer 按后进先出(LIFO)的栈顺序执行:
func main() {
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
}
// 输出: 2 1 0
4.7.2 defer 参数的求值时机
defer 语句的参数在 defer 出现时立即求值,而非执行时:
func example() {
i := 0
defer fmt.Println("defer 中的 i:", i) // 此时 i 为 0
i = 100
// 输出: defer 中的 i: 0
}
defer 还能配合命名返回值修改返回结果,这是实现 recover 错误恢复的关键(详见第 7 章)。
4.8 本章小结
本章学习了 Go 的流程控制(if、for、switch)和函数。Go 用单一的 for 表达所有循环,switch 默认自动 break 且支持类型判断。函数支持多返回值、可变参数、闭包,并通过 defer 优雅地管理资源释放。这些特性共同构成了 Go 简洁而强大的控制流。
下一章我们将深入复合数据类型:数组、切片、map 和结构体。