Go语言控制结构

控制结构决定了程序的执行路径。Go语言的设计者推崇“少即是多”的理念,因此只保留了最必要的控制语句:条件判断(if/else)、循环(for)、分支选择(switch)、延迟执行(defer)以及极少使用的跳转(goto)。本章将逐一深入讲解,并通过实例展示Go风格的代码组织方式。

Go的特点:没有while/do-while(均用for实现),没有三元运算符(?:),if/for语句的条件不需要括号,但分支体必须使用花括号。

if 条件语句

Go的if语句与C语言类似,但取消了条件表达式周围的括号,并且可以在条件判断之前执行一个简单的初始化语句。

基本形式

if 示例
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关键字和条件表达式之间可以添加一个简短的语句(通常是变量声明),该变量的作用域仅限于if-else块内部。

if err := processFile(); err != nil {
    fmt.Println("错误:", err)
    return
} else {
    // 这里也可以访问 err
    fmt.Println("处理成功")
}
// 在此处无法访问 err

这种写法非常适用于需要调用函数并检查返回错误的场景,能让错误处理的代码更紧凑。

for 循环语句

Go语言只有for一种循环关键字,但可以以多种形式使用,足以替代其他语言的while和do-while。

2.1 经典三语句循环

for i := 0; i < 5; i++ {
    fmt.Println("i =", i)
}

2.2 类似while的循环(条件循环)

省略初始化语句和后置语句,只保留条件,即成为while循环。

sum := 1
for sum < 100 {
    sum += sum
}
fmt.Println(sum) // 128

2.3 无限循环

省略所有三个部分,形成无限循环,通常与break配合使用。

count := 0
for {
    count++
    if count >= 3 {
        break
    }
    fmt.Println("loop")
}

2.4 range 循环(遍历数组、切片、字符串、map、通道)

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)
}

2.5 循环中的break和continue

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

switch 分支语句

Go的switch比C/C++更灵活:每个case默认自带break(不会贯穿到下一个case),且case表达式可以是任意类型(不限于整数),甚至可以是布尔条件。

3.1 基本用法

day := "Saturday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
    fmt.Println("工作日")
case "Saturday", "Sunday":
    fmt.Println("周末")
default:
    fmt.Println("无效的日期")
}

3.2 无表达式switch(替代if-else链)

如果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")
}

3.3 带初始化语句的switch

类似于if,可以在switch前加一个简短语句。

switch x := 2; x {
case 1:
    fmt.Println("one")
case 2:
    fmt.Println("two")
default:
    fmt.Println("other")
}

3.4 fallthrough 关键字

如果希望执行完一个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语句会将一个函数调用推迟到外层函数返回之前执行,常用于资源释放(如关闭文件、解锁互斥锁)。多个defer以后进先出(LIFO)的顺序执行。

defer 示例
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 语句

goto可以跳转到同一函数内定义的标签。过度使用会导致代码难以理解,但有些场景(如跳出多层循环或统一错误处理)可以使代码更清晰。

func main() {
    i := 0
    for {
        i++
        if i == 3 {
            goto Exit
        }
        fmt.Println(i)
    }
Exit:
    fmt.Println("跳转到此处,循环结束")
}
建议:尽量避免使用goto,除非能显著提升代码清晰度(例如在错误处理中跳转到统一的清理代码块)。

综合示例:猜数字小游戏

综合运用if/for/break
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次猜对了!
游戏结束,感谢参与!

小结:掌握Go的控制结构是编写逻辑正确程序的基础。Go刻意简化了控制语句的多样性,鼓励使用for和if完成大多数流程控制。下一章我们将学习Go语言中强大的函数特性——多返回值、命名返回值、可变参数、闭包等。