4、流程控制:if-else条件判断、for循环(三种形式)、switch语句、goto、break、continue

流程控制,说白了就是让代码「走哪条路」或者「走几遍」。我见过不少新手,一上来就写几百行的线性代码,逻辑全堆在一起,最后自己都看不懂。其实用好流程控制,代码会清晰很多。

Go 语言的流程控制关键字不多,但每个都很实用。咱们一个一个来看。

4.1 if-else 条件判断

Go 的 if 语句有个特点——条件表达式不需要括号。这跟 C 语言、Java 都不一样。我个人习惯是直接写,不加括号,干净利落。

package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("优秀")
    } else if score >= 80 {
        fmt.Println("良好")
    } else if score >= 60 {
        fmt.Println("及格")
    } else {
        fmt.Println("不及格")
    }
}

这里有个小细节:else if 必须跟前面的右花括号在同一行。你不能写成:

if score >= 90 {
    // ...
}
else {  // 编译错误!
    // ...
}

嗯,Go 的编译器就是这么「强迫症」。不过习惯了就好,其实这样写出来的代码更整齐。

小技巧:Go 的 if 支持在条件前加一个简单语句。比如:
if err := doSomething(); err != nil {
    fmt.Println("出错了:", err)
}
这个写法在 Go 里非常常见,尤其是处理错误时。变量 err 的作用域只在 if-else 块内,不会污染外部。

我在项目中遇到过一个问题:有人把 if err == nilif err != nil 混着用,导致逻辑混乱。我的建议是——优先处理错误情况。也就是先写 if err != nil { return },把正常逻辑放在后面。这样代码的嵌套层次会少很多。

4.2 for 循环(三种形式)

Go 语言只有 for 这一个循环关键字。没有 while,没有 do-while。但别担心,一个 for 能搞定所有场景。它有三种形式。

形式一:标准 for 循环

跟 C 语言很像,但同样没有括号。

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

这里 i := 0 是初始化语句,i < 5 是条件,i++ 是后置语句。三个部分用分号隔开。

形式二:类似 while 的循环

把初始化语句和后置语句都省略,就变成了 while 循环。

sum := 0
for sum < 100 {
    sum += 10
}
fmt.Println(sum) // 输出 100

说白了,这就是 Go 里的 while。你想想看,少学一个关键字,是不是挺省心的?

形式三:无限循环

连条件都不写,就是无限循环。

for {
    fmt.Println("停不下来...")
    // 通常配合 break 使用
}

我曾经在写一个后台服务时,就用无限循环加 time.Sleep 来做定时任务。当然,记得加退出条件,不然程序真的停不下来。

重要提醒:for 循环的变量作用域。在 Go 1.22 之前,循环变量是共享的。比如:
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i) // 可能输出 3,3,3
    }()
}
从 Go 1.22 开始,每次迭代都会创建新的变量。如果你还在用旧版本,记得在 goroutine 里把 i 作为参数传进去。

4.3 switch 语句

Go 的 switch 比很多语言都灵活。它不需要在每个 case 后面写 break,默认只会执行匹配的那个 case。这一点跟 C 语言完全不同。

day := 3

switch day {
case 1:
    fmt.Println("星期一")
case 2:
    fmt.Println("星期二")
case 3:
    fmt.Println("星期三")
case 4:
    fmt.Println("星期四")
case 5:
    fmt.Println("星期五")
default:
    fmt.Println("周末")
}

如果你想「穿透」到下一个 case,可以用 fallthrough 关键字。不过我个人很少用,因为容易让逻辑变得混乱。

switch 还有一种更灵活的用法——不带表达式。这时候每个 case 就是一个条件判断。

score := 85

switch {
case score >= 90:
    fmt.Println("优秀")
case score >= 80:
    fmt.Println("良好")
case score >= 60:
    fmt.Println("及格")
default:
    fmt.Println("不及格")
}

你看,这跟 if-else 链效果一样,但看起来更整齐。我建议在条件比较多的时候用 switch,代码可读性会好很多。

小技巧:switch 也可以像 if 一样,在条件前加一个简单语句。
switch score := getScore(); {
case score >= 90:
    // ...
}
这样 score 的作用域就限制在 switch 块内了。

4.4 goto 语句

goto 在不少语言里被「妖魔化」了。其实它没那么可怕。Go 保留了 goto,但限制了它的使用——不能跳转到另一个函数,也不能跳过一个变量声明。

for i := 0; i < 5; i++ {
    for j := 0; j < 5; j++ {
        if i*j > 10 {
            goto exit
        }
        fmt.Println(i, j)
    }
}
exit:
fmt.Println("跳出双层循环")

我曾经在解析复杂配置文件时用过 goto。当时需要从多层嵌套循环中直接跳出,用 break 要加标签,用 goto 反而更直观。当然,日常开发中我很少用 goto。你想想看,能用 break 或 return 解决的问题,就别用 goto。

注意:goto 不能跳入另一个代码块,也不能跳过变量声明。比如这样是不行的:
goto skip
x := 10 // 编译错误:跳过了 x 的声明
skip:
fmt.Println(x)

4.5 break 和 continue

这两个关键字在循环里很常用。break 跳出当前循环,continue 跳过本次循环的剩余部分,进入下一次迭代。

for i := 0; i < 10; i++ {
    if i == 3 {
        continue // 跳过 3
    }
    if i == 7 {
        break // 到 7 就停止
    }
    fmt.Println(i)
}
// 输出:0 1 2 4 5 6

在嵌套循环中,break 和 continue 默认只作用于最内层的循环。如果你想跳出外层循环,需要配合标签使用。

outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer // 跳出外层循环
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// 输出:(0,0) (0,1) (0,2) (1,0)

我记得刚学 Go 的时候,总觉得标签有点多余。后来在写一个矩阵运算的程序时,三层循环嵌套,用标签配合 break 一下子就搞定了。嗯,有些东西用到了才知道它的好。

总结一下:
  • if-else:条件判断,优先处理错误情况
  • for:三种形式,覆盖所有循环场景
  • switch:比 if-else 更清晰,默认不穿透
  • goto:慎用,适合跳出多层循环
  • break/continue:控制循环流程,配合标签使用更强大

流程控制是编程的基础。说白了,代码就是「顺序、选择、循环」这三种结构的组合。把这些掌握好,你就能写出逻辑清晰、可读性强的 Go 代码了。