Go语言判断语句

判断语句是程序控制流的基础。Go提供了 ifswitch 两种判断结构,语法上与C/Java有不少差异——不需要括号、默认不穿透、支持初始化语句等。本文将系统介绍Go判断语句的写法、逻辑运算符、以及与Java的对比。


if 语句

基本语法

Go的 if 不需要小括号包裹条件,但花括号是必须的

age := 20

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

if age >= 18 {
fmt.Println("成年")
} else {
fmt.Println("未成年")
}

if age < 12 {
fmt.Println("儿童")
} else if age < 18 {
fmt.Println("青少年")
} else {
fmt.Println("成年")
}

注意:左花括号 { 必须与 if/else 在同一行,不能换行。这是Go编译器的强制要求,不是风格偏好:

// 编译错误
if age >= 18
{
fmt.Println("成年")
}

原因是Go在编译时会自动在行尾插入分号。如果 { 换行,编译器会在 if age >= 18 后面插入分号,导致语法错误。

if 初始化语句

Go的 if 支持在条件前加一条初始化语句,用分号分隔。初始化语句中声明的变量作用域仅限于整个 if-else 块

// 推荐写法:err 作用域限定在 if-else 块内
if err := doSomething(); err != nil {
fmt.Println("出错了:", err)
return
}
// 这里访问不到 err,避免变量污染外部作用域

// 对比:不使用初始化语句
err := doSomething()
if err != nil {
fmt.Println("出错了:", err)
return
}
// err 在这里仍然可见,但后续可能不再需要

这种写法在Go中非常常见,尤其是错误处理和类型断言场景:

// 类型断言
if v, ok := x.(string); ok {
fmt.Println("是字符串:", v)
}

// map 取值
if val, ok := m["key"]; ok {
fmt.Println("找到了:", val)
}

逻辑运算符

Go提供三个逻辑运算符,与大多数语言一致:

运算符 含义 示例
&& 逻辑与(AND) a && b:a和b都为true时结果为true
|| 逻辑或(OR) a || b:a或b有一个为true时结果为true
! 逻辑非(NOT) !a:取反,true变false,false变true
age := 25
hasID := true

// && :两个条件都满足
if age >= 18 && hasID {
fmt.Println("允许进入")
}

// || :满足任一条件
if age < 12 || age > 65 {
fmt.Println("免票")
}

// ! :取反
isBlocked := false
if !isBlocked {
fmt.Println("未被封禁")
}

优先级

! > && > ||,与数学中”非 > 与 > 或”一致。建议在复杂表达式中使用括号明确意图:

// 不加括号,依赖优先级
if a || b && c { // 等价于 a || (b && c)
}

// 推荐:加括号,意图更清晰
if a || (b && c) {
}

短路求值(Short-circuit Evaluation)

Go的逻辑运算符 &&|| 采用短路求值——如果通过左侧表达式已经能确定结果,右侧表达式不会被执行。

&& 短路

&& 的左侧为 false 时,整个表达式必定为 false,右侧不再执行:

func isValid() bool {
fmt.Println("isValid 被调用了")
return true
}

x := 0
if x > 0 && isValid() {
// x > 0 为 false,isValid() 不会被调用
}
// 不会打印 "isValid 被调用了"

|| 短路

|| 的左侧为 true 时,整个表达式必定为 true,右侧不再执行:

x := 1
if x > 0 || isValid() {
// x > 0 为 true,isValid() 不会被调用
fmt.Println("条件成立")
}
// 不会打印 "isValid 被调用了"

短路的实际应用

短路求值不只是性能优化,更是一种安全保护——可以避免空指针等运行时错误:

// 先判断 nil,再访问字段,避免 panic
if user != nil && user.Age > 18 {
fmt.Println("成年用户")
}
// 如果 user 为 nil,user.Age 不会被执行,不会 panic

// 先判断长度,再访问元素,避免越界
if len(s) > 0 && s[0] == "admin" {
fmt.Println("管理员")
}

如果没有短路机制,上面的写法都会在条件不满足时触发 panic。


switch 语句

基本语法

day := "Monday"

switch day {
case "Monday":
fmt.Println("星期一")
case "Tuesday":
fmt.Println("星期二")
case "Wednesday":
fmt.Println("星期三")
default:
fmt.Println("其他")
}

一个 case 匹配多个值

switch day {
case "Saturday", "Sunday":
fmt.Println("周末")
default:
fmt.Println("工作日")
}

switch 初始化语句

if 一样,switch 也支持初始化语句:

switch today := time.Now().Weekday(); today {
case time.Saturday, time.Sunday:
fmt.Println("周末")
default:
fmt.Println("工作日")
}
// today 在这里不可见

无表达式 switch

省略 switch 后面的表达式时,每个 case 可以是独立的条件表达式,相当于 if-else if-else 的替代写法:

score := 85

switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 60:
fmt.Println("C")
default:
fmt.Println("D")
}

当分支超过3个时,无表达式 switch 比 if-else 链更清晰。

Type Switch(类型判断)

用于判断 interface{} 的具体类型,是Go独有的 switch 用法:

func describe(i interface{}) string {
switch v := i.(type) {
case int:
return fmt.Sprintf("整数: %d", v)
case string:
return fmt.Sprintf("字符串: %s, 长度: %d", v, len(v))
case bool:
return fmt.Sprintf("布尔值: %t", v)
case nil:
return "nil"
default:
return fmt.Sprintf("未知类型: %T", v)
}
}

fmt.Println(describe(42)) // 整数: 42
fmt.Println(describe("hello")) // 字符串: hello, 长度: 5
fmt.Println(describe(true)) // 布尔值: true

注意i.(type) 语法只能在 switch 中使用,不能单独使用。单一类型判断用类型断言 v, ok := i.(int)


fallthrough

Go的switch 默认不穿透,每个case执行完自动break。需要穿透时使用 fallthrough 关键字:

x := 1

switch x {
case 1:
fmt.Println("一")
fallthrough
case 2:
fmt.Println("二")
fallthrough
case 3:
fmt.Println("三")
}
// 输出:
// 一
// 二
// 三

fallthrough 的三条规则

1. 无条件跳入下一个 case 的代码体,不检查条件

switch 1 {
case 1:
fmt.Println("命中 case 1")
fallthrough
case 99:
fmt.Println("命中 case 99") // 值不是99,但依然执行
}
// 输出:
// 命中 case 1
// 命中 case 99

2. 必须是 case 块的最后一条语句

switch x {
case 1:
fallthrough
fmt.Println("这行不会执行") // 编译错误:fallthrough 后面不能有语句
case 2:
fmt.Println("二")
}

3. 不能用在 type switch 中

switch v := i.(type) {
case int:
fallthrough // 编译错误:cannot fallthrough in type switch
case string:
fmt.Println(v)
}

实际开发中 fallthrough 很少使用。大多数需要穿透的场景可以通过一个 case 匹配多个值来替代:case 1, 2, 3:


Go与Java判断语句的对比

if 对比

特性 Go Java
条件括号 不需要 if x > 0 {} 必须 if (x > 0) {}
花括号 必须,即使只有一行 单行可省略(不推荐)
初始化语句 支持 if err := f(); err != nil {} 不支持
条件类型 必须是 bool 必须是 boolean
隐式类型转换 不支持,if 1 {} 编译错误 不支持,if (1) {} 编译错误

Go和Java在这一点上一致:条件必须是布尔类型,不像C/JavaScript那样允许整数或对象做条件。

switch 对比

特性 Go Java
默认行为 不穿透,自动break 穿透,需要手动break
穿透控制 需要 fallthrough 显式穿透 需要 break 显式停止
case 值类型 任意表达式,可以不是常量 Java 14之前需要常量(int/enum/String)
多值匹配 case 1, 2, 3: Java 14+ case 1, 2, 3 ->
无表达式 switch 支持 switch {} 不支持
type switch 支持 switch v := i.(type) 通过 instanceof + if-else 实现
switch 表达式 不支持(switch是语句) Java 14+ 支持 switch 表达式有返回值

典型代码对比

Go 风格

switch day {
case "Saturday", "Sunday":
fmt.Println("周末")
default:
fmt.Println("工作日")
}

Java 风格(传统)

switch (day) {
case "Saturday":
case "Sunday":
System.out.println("周末");
break; // 必须手动 break
default:
System.out.println("工作日");
break;
}

Java 14+ 新语法

switch (day) {
case "Saturday", "Sunday" -> System.out.println("周末");
default -> System.out.println("工作日");
}

Java 14+ 的箭头语法借鉴了Go的”默认不穿透”设计。


面试高频题

Q1:Go 的 if 和 Java 的 if 有什么区别?

:主要有三点区别。第一,Go的 if 条件不需要小括号,但花括号是强制的,即使只有一行代码;Java 的 if 条件必须加小括号,单行代码可以省略花括号。第二,Go支持 if 初始化语句 if err := f(); err != nil {},Java 不支持。第三,两者的条件都必须是布尔类型,不支持隐式类型转换。

Q2:下面代码能编译通过吗?为什么?

x := 1
if x {
fmt.Println("true")
}

:不能。Go不支持条件表达式的隐式类型转换,if 的条件必须是 bool 类型。整数 1 不是 bool,编译器报错 non-bool x (variable of type int) used as condition。必须写成 if x != 0 {}

Q3:短路求值是什么?有什么实际作用?

:短路求值是指 && 的左侧为 false 时右侧不执行,|| 的左侧为 true 时右侧不执行。除了减少不必要的计算外,最重要的作用是安全保护。例如 if p != nil && p.Name == "admin",如果没有短路机制,当 p 为 nil 时访问 p.Name 会触发 panic。短路保证了左侧条件不满足时,右侧不会执行。

Q4:Go 的 switch 为什么默认不穿透?

:在C/Java中,switch 默认穿透、需要手动写 break 是大量 bug 的来源——忘记写 break 导致意外执行下一个 case 是非常常见的错误。Go的设计哲学是”让正确的写法成为默认行为”,所以默认不穿透。极少数需要穿透的场景,通过显式的 fallthrough 关键字实现,意图更清晰。

Q5:fallthrough 的行为是什么?它会重新判断下一个 case 的条件吗?

:不会。fallthrough无条件跳入下一个 case 的代码体,不会重新评估下一个 case 的条件。例如 switch 1case 1 使用了 fallthrough,即使下一个是 case 99(值不匹配),也会执行 case 99 的代码。另外,fallthrough 必须是 case 块的最后一条语句,且不能用在 type switch 中。

Q6:下面代码输出什么?

func check() bool {
fmt.Println("check 被调用")
return true
}

func main() {
x := 10
if x > 5 || check() {
fmt.Println("条件成立")
}
}

:只输出 条件成立,不会输出 check 被调用。因为 || 短路求值——x > 5 为 true,整个表达式已经确定为 true,右侧 check() 不会被调用。

Q7:if 初始化语句中声明的变量,在 else 块中能访问吗?

if val, ok := m["key"]; ok {
fmt.Println(val)
} else {
fmt.Println(val) // 这里能访问 val 吗?
}

:能。if 初始化语句中声明的变量,作用域是整个 if-else 链,包括 ifelse ifelse 所有分支。出了 if-else 块才不可见。这里 valok 在 else 块中都能访问,val 会是类型的零值。

Q8:type switch 和类型断言有什么区别?

:类型断言 v, ok := i.(int) 用于判断单一类型,如果不使用 comma ok 模式(直接 v := i.(int)),类型不匹配时会 panic。Type switch switch v := i.(type) 可以同时匹配多种类型,不会 panic,匹配不到走 default。需要判断多种类型时用 type switch,判断单一类型时用类型断言的 comma ok 模式。此外,fallthrough 不能用在 type switch 中。


小结

概念 要点
if 语法 条件不加括号,花括号必须,左花括号不能换行
if 初始化语句 if x := f(); x > 0 {},变量作用域限定在 if-else 块内
逻辑运算符 ! > && > `
短路求值 && 左侧为false则右侧不执行,`
switch 默认行为 自动break,不穿透
fallthrough 无条件进入下一个case的代码体,不重新判断条件,不可用于type switch
无表达式 switch switch {} 等价于 if-else 链,分支多时更清晰
type switch switch v := i.(type) 判断接口的具体类型
Go vs Java Go默认不穿透、不需要括号、支持初始化语句和type switch

下一篇将介绍Go的循环语句(for)与跳转控制