跳到主要内容

Conditional Control(条件控制)

Go 使用 ifswitch 控制条件分支。条件必须产生 bool,不能把数字、字符串、指针或集合当作隐式真假值。Go 没有三元条件运算符,分支逻辑应通过明确的控制流表达。

基本 if 语句

if 条件不需要圆括号,代码块的花括号不可省略:

package main

import "fmt"

func main() {
age := 20

if age >= 18 {
fmt.Println("adult")
}
}

条件必须是布尔表达式:

count := 1

if count > 0 {
fmt.Println("has items")
}

// if count { } // 编译错误:count 不是 bool

这条规则避免了 0、空字符串和 nil 的隐式真值转换。

else ifelse

多个互斥条件可以组成分支链:

package main

import "fmt"

func main() {
score := 82

if score >= 90 {
fmt.Println("excellent")
} else if score >= 60 {
fmt.Println("passed")
} else {
fmt.Println("failed")
}
}

Go 的分号插入规则要求 else 与前一个右花括号位于同一行。标准格式应写成 } else {,并交给 gofmt 统一排版。

条件顺序会影响结果。范围判断通常从更严格的条件开始,避免较宽条件提前匹配。条件来自外部输入时,还应先验证输入范围。

带初始化语句的 if

if 可以在条件前执行一条简单语句,两者使用分号分隔:

package main

import (
"fmt"
"strconv"
)

func main() {
if value, err := strconv.Atoi("42"); err != nil {
fmt.Println("invalid number:", err)
} else {
fmt.Println(value * 2)
}
}

初始化语句声明的变量只在整个 ifelse ifelse 结构中可见。把临时结果和错误限制在最小作用域内,可以减少误用;如果后续代码仍需要该值,就应在 if 之前声明。

提前返回

处理错误和无效输入时,提前返回通常能让正常路径保持平直:

package main

import (
"errors"
"fmt"
)

func priceWithDiscount(price float64, member bool) (float64, error) {
if price < 0 {
return 0, errors.New("price must not be negative")
}

if !member {
return price, nil
}

return price * 0.9, nil
}

func main() {
price, err := priceWithDiscount(100, true)
if err != nil {
fmt.Println(err)
return
}

fmt.Println(price)
}

当一个分支已经 returnbreakcontinuepanic 后,通常不需要再用 else 包住后续正常路径。提前返回不是机械规则;出口过多导致资源清理或状态变化难以追踪时,应重新划分函数职责。

表达式 switch

switch 适合对一个值进行多个互斥匹配:

package main

import "fmt"

func main() {
method := "POST"

switch method {
case "GET":
fmt.Println("read")
case "POST", "PUT":
fmt.Println("write")
default:
fmt.Println("unsupported")
}
}

每个 case 可以包含多个逗号分隔的表达式。Go 的 switch 默认不会继续执行下一个 case,因此通常不写 breakdefault 可放在任意位置,但通常放在最后便于阅读。

无表达式 switch

省略表达式时,switch 等价于对 true 进行匹配,可以替代较长的 if else 链:

package main

import "fmt"

func main() {
temperature := 31

switch {
case temperature >= 35:
fmt.Println("hot")
case temperature >= 20:
fmt.Println("warm")
default:
fmt.Println("cool")
}
}

分支从上到下求值,只执行第一个匹配的 case。因此条件有包含关系时,仍应把更严格的条件放在前面。

switch 也可以包含初始化语句:

switch value := loadStatus(); value {
case "ready":
fmt.Println("start")
default:
fmt.Println("wait")
}

value 的作用域覆盖整个 switch

fallthrough

fallthrough 会无条件进入下一个 case 的语句体,不会重新检查下一个 case 的条件:

switch level {
case "debug":
fmt.Println("debug details")
fallthrough
case "info":
fmt.Println("general message")
}

它只能作为非最后一个表达式 switch 分支的最后一条非空语句使用。大多数逻辑可以通过合并 case、提取函数或显式调用表达得更清楚,应谨慎使用。

类型 switch

类型分支用于检查接口值保存的动态类型:

package main

import "fmt"

func describe(value any) {
switch current := value.(type) {
case int:
fmt.Println("int:", current)
case string:
fmt.Println("string:", current)
case nil:
fmt.Println("nil")
default:
fmt.Printf("other: %T\n", current)
}
}

func main() {
describe(42)
describe("Go")
}

value.(type) 只能出现在类型 switch 的守卫中。分支变量 current 在单一具体类型的 case 中具有该具体类型;在包含多个类型的 casedefault 中保持接口类型。类型分支适合处理确实允许多种动态类型的边界,不应替代清晰的接口方法设计。

接口值、typed nil 和普通类型断言的完整规则见 Interface(接口)

常见错误

  • 把整数、字符串或 nil 直接写成 if 条件;Go 不做隐式真值转换。
  • 省略花括号,或把 else 放到前一个右花括号的下一行。
  • 忘记初始化语句中的变量只在当前条件结构内可见。
  • 按照 JavaScript 习惯为每个 case 添加 break,或误以为 Go 默认贯穿后续分支。
  • 滥用 fallthrough,并错误地认为下一个 case 条件还会被检查。
  • 使用很长的类型 switch 代替由接口表达的稳定行为契约。

与 TypeScript/JavaScript 的关键差异

  • Go 条件必须是 bool,没有 JavaScript 的 truthy/falsy 规则。
  • Go 没有 ?: 三元条件运算符,也没有通过 &&|| 返回任意操作数的用法。
  • Go 的 switch 默认不贯穿下一个分支,JavaScript 通常需要显式 break 才能阻止贯穿。
  • Go 的 ifswitch 可以带初始化语句,并把变量作用域限制在当前控制结构。
  • 类型 switch 检查接口值的动态类型,不等同于 TypeScript 编译期的联合类型收窄。

小结

  • if 条件必须是 bool,条件外不写圆括号,代码块花括号不可省略。
  • 初始化语句可以把临时值和错误限制在当前 ifswitch 中。
  • 提前返回适合处理错误和边界条件,使正常路径减少嵌套。
  • switch 默认只执行第一个匹配分支,不会自动贯穿。
  • 无表达式 switch 适合组织互斥布尔条件,条件顺序仍然重要。
  • 类型 switch 用于有限的动态类型分派,不应取代清晰的接口设计。