Go 从 0 到精通 · 第 04 课:条件判断 ifswitch

学习定位:这是整套 Go 教程的第 4 课。
前置要求:已经完成第 3 课,掌握了 fmt.Print/Println/Printf 的区别、控制台输入、以及显式类型转换。
本课目标:掌握 Go 中 ifswitch 两种条件判断方式,理解 Go 特有的写法和设计思想,能根据不同条件控制程序执行路径。


1. 本课你要解决的核心问题

前三课你写的程序都是"从头到尾顺序执行"的。但真实的程序几乎不可能只有一条执行路径。

你需要搞明白以下几个问题:

  • if 在 Go 里的基本写法和其他语言有什么区别
  • 什么是 if 的初始化语句,为什么 Go 要这样设计
  • else ifelse 怎么组合使用
  • switch 的基本写法和它在 Go 中的特殊之处
  • 为什么 Go 的 switch 不需要 break
  • 什么时候用 if,什么时候用 switch

学完这一课,你的程序就能"做决定"了。


2. if 的基本写法

先看最简单的形式。

2.1 单条件判断

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
age := 18

if age >= 18 {
fmt.Println("你已经成年了")
}
}

输出:

1
你已经成年了

这段代码的意思是:如果 age >= 18 成立,就执行大括号里的代码。


2.2 Go 的 if 和其他语言的区别

如果你来自 Java、C、C++ 等语言,你可能习惯这样写:

1
2
3
if (age >= 18) {
// ...
}

但 Go 里条件表达式不需要小括号

1
2
3
if age >= 18 {
// ...
}

加了括号虽然不会报错,但不符合 Go 的风格。Go 代码格式化工具通常也会帮你去掉多余的括号。

另外一个重要区别:大括号不能省略

在某些语言里,如果 if 后面只有一行代码,可以不写大括号。Go 不允许这样做:

1
2
3
// 错误写法
if age >= 18
fmt.Println("成年了")

Go 要求:

  • 条件表达式不加括号
  • 大括号必须有
  • 左大括号 { 必须和 if 在同一行

这三个规则从第一天就要养成习惯。


3. if...else

3.1 基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
age := 16

if age >= 18 {
fmt.Println("你已经成年了")
} else {
fmt.Println("你还未成年")
}
}

输出:

1
你还未成年

else 表示"否则",当 if 条件不成立时执行。


3.2 else 的位置很重要

Go 要求 else 必须紧跟在上一个 } 后面,写在同一行:

1
2
3
4
5
6
// 正确写法
if age >= 18 {
fmt.Println("成年")
} else {
fmt.Println("未成年")
}
1
2
3
4
5
6
7
// 错误写法
if age >= 18 {
fmt.Println("成年")
}
else {
fmt.Println("未成年")
}

else 换到下一行会导致编译错误。这和 Go 自动插入分号的机制有关:编译器会在 } 后面自动加分号,导致 else 变成了"孤立的关键字"。

你不需要深究原理,只要记住:

} else { 必须写在同一行。


4. if...else if...else

当条件不止两种时,用 else if 来添加更多分支。

4.1 基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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("不及格")
}
}

输出:

1
良好

执行逻辑是从上往下依次判断,一旦某个条件成立,就执行对应的代码块,后面的分支不再判断。


4.2 条件判断的顺序很重要

看这个反面例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
score := 95

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

输出:

1
及格

虽然 score95,但因为第一个条件 score >= 60 已经成立,程序就直接执行第一个分支,后面的分支不会被检查。

这说明:

条件分支的顺序必须从严格到宽松,否则宽松条件会"吃掉"后面的精确条件。


5. Go 特有的 if 初始化语句

这是 Go if 语句最独特的特性之一,很多其他语言没有。

5.1 基本写法

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
if age := 20; age >= 18 {
fmt.Println("成年了,年龄是", age)
}
}

输出:

1
成年了,年龄是 20

注意这里的写法:

1
if age := 20; age >= 18 {

分号前面是一个初始化语句(声明变量),分号后面是条件表达式。


5.2 为什么要这样设计

这种写法的核心好处是:限制变量的作用域

age 只在 if 语句块内部可用,出了 if 就不存在了:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
if age := 20; age >= 18 {
fmt.Println("成年了,年龄是", age)
}

// 这里访问 age 会报错
// fmt.Println(age) // 编译错误:undefined: age
}

这样做的好处是:

  • 变量不会"泄漏"到不需要它的地方
  • 代码更干净
  • 后面学到错误处理时,这种写法会非常常见

5.3 最典型的实战用法

在 Go 中,你以后会经常看到这样的代码:

1
2
3
4
if err := doSomething(); err != nil {
fmt.Println("出错了:", err)
return
}

这里先执行 doSomething(),把返回的错误赋给 err,然后立刻判断 err 是否为 nil

err 只在这个 if 块里有效,不会污染外部作用域。

你现在先留个印象,等学到函数和错误处理时会大量使用这种模式。


5.4 初始化语句也可以用在 else

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
if num := 15; num%2 == 0 {
fmt.Println(num, "是偶数")
} else {
fmt.Println(num, "是奇数")
}
}

输出:

1
15 是奇数

else 分支里,num 依然可用。初始化语句声明的变量在整个 if...else 链中都有效。


6. 条件表达式中的逻辑运算符

在条件判断中,你经常需要组合多个条件。Go 支持以下逻辑运算符:

运算符 含义 示例
&& 与(两个都为真才为真) a > 0 && b > 0
` `
! 非(取反) !isLogin

6.1 &&:与

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
age := 20
hasID := true

if age >= 18 && hasID {
fmt.Println("可以进入")
} else {
fmt.Println("不可以进入")
}
}

输出:

1
可以进入

两个条件都满足时,整体才为 true


6.2 ||:或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
isVip := false
hasCoupon := true

if isVip || hasCoupon {
fmt.Println("可以享受优惠")
} else {
fmt.Println("没有优惠")
}
}

输出:

1
可以享受优惠

只要有一个条件满足,整体就为 true


6.3 !:非

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
isLogin := false

if !isLogin {
fmt.Println("请先登录")
}
}

输出:

1
请先登录

!false 变成 true,把 true 变成 false


6.4 组合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
age := 25
isStudent := true
hasCard := false

if (age >= 18 && isStudent) || hasCard {
fmt.Println("符合条件")
} else {
fmt.Println("不符合条件")
}
}

当条件比较复杂时,建议用括号明确分组,提高可读性。


7. 嵌套 if

if 里面可以再嵌套 if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
age := 20
hasTicket := true

if age >= 18 {
if hasTicket {
fmt.Println("可以入场")
} else {
fmt.Println("成年了,但没票")
}
} else {
fmt.Println("未成年,不能入场")
}
}

输出:

1
可以入场

嵌套 if 有时不可避免,但如果嵌套太深,代码可读性会变差。在实际开发中,Go 社区更推荐"尽早返回"的风格来减少嵌套层级。这个技巧在后面学函数时会详细讲。


8. switch 基本用法

switch 是另一种条件判断方式,适合处理多个离散值的匹配场景。

8.1 最基本的 switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import "fmt"

func main() {
day := 3

switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3:
fmt.Println("星期三")
case 4:
fmt.Println("星期四")
case 5:
fmt.Println("星期五")
case 6:
fmt.Println("星期六")
case 7:
fmt.Println("星期日")
default:
fmt.Println("无效的日期")
}
}

输出:

1
星期三

switch 会从上到下匹配 case,找到匹配的就执行对应代码块。如果没有任何 case 匹配,就执行 default


8.2 Go 的 switch 不需要 break

这是 Go switch 和很多其他语言最大的区别。

在 C、Java 等语言中,switch 的每个 case 后面通常需要写 break,否则会"穿透"到下一个 case

Go 的设计是:

每个 case 执行完就自动结束,不需要写 break

这意味着:

1
2
3
4
5
6
switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
}

day1 时,只会打印"星期一",不会继续执行"星期二"。

Go 为什么这样设计?因为在实际编程中,"穿透"行为绝大多数时候是程序员忘了写 break 导致的 bug,而不是有意为之。Go 选择了更安全的默认行为。


8.3 如果真的需要穿透:fallthrough

在极少数场景下,如果你确实想让执行"穿透"到下一个 case,可以使用 fallthrough

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
num := 1

switch num {
case 1:
fmt.Println("匹配到 1")
fallthrough
case 2:
fmt.Println("匹配到 2")
case 3:
fmt.Println("匹配到 3")
}
}

输出:

1
2
匹配到 1
匹配到 2

fallthrough 会强制执行下一个 case 的代码,不管下一个 case 的条件是否匹配。

但在实际开发中,fallthrough 很少使用。你知道它存在就好,不要养成依赖它的习惯。


9. switch 的多值匹配

一个 case 可以同时匹配多个值,用逗号分隔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
day := 6

switch day {
case 1, 2, 3, 4, 5:
fmt.Println("工作日")
case 6, 7:
fmt.Println("周末")
default:
fmt.Println("无效日期")
}
}

输出:

1
周末

这比写多个 case 简洁得多,也比用一堆 ||if 更清晰。


10. switch 不写表达式:当 if...else if

Go 的 switch 有一个很特别的用法:可以不写匹配表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
score := 85

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

输出:

1
良好

这里 switch 后面没有写表达式,每个 case 后面是一个布尔条件。

这种写法本质上等价于 if...else if...else,但在分支比较多的时候,switch 写法更整齐。

你可以根据个人偏好选择:

  • 分支少(2~3 个):if...else if 通常更自然
  • 分支多(4 个以上):无表达式 switch 更整洁

11. switch 也支持初始化语句

if 一样,switch 也可以在前面加一个初始化语句:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
switch num := 15; {
case num%2 == 0:
fmt.Println(num, "是偶数")
case num%2 != 0:
fmt.Println(num, "是奇数")
}
}

输出:

1
15 是奇数

num 的作用域只限于这个 switch 语句块内部。

带表达式的 switch 也可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
switch day := 3; day {
case 1, 2, 3, 4, 5:
fmt.Println("工作日")
case 6, 7:
fmt.Println("周末")
}
}

12. switch 匹配字符串

switch 不限于匹配数字,也可以匹配字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
lang := "Go"

switch lang {
case "Go":
fmt.Println("你在学习 Go 语言")
case "Python":
fmt.Println("你在学习 Python")
case "Java":
fmt.Println("你在学习 Java")
default:
fmt.Println("未知语言:", lang)
}
}

输出:

1
你在学习 Go 语言

这在处理命令、状态码、配置值等场景时非常实用。


13. ifswitch 怎么选

这是初学者经常会问的问题。下面给你一个简单的判断标准:

13.1 用 if 的场景

  • 条件是范围判断(大于、小于、区间)
  • 条件涉及多个不同变量的组合
  • 只有 2~3 个分支
  • 条件逻辑比较复杂,需要 &&||

13.2 用 switch 的场景

  • 一个变量需要和多个固定值匹配
  • 分支比较多(4 个以上)
  • 逻辑是"这个值等于什么"的模式
  • 希望代码更整齐

13.3 一句话总结

if 更灵活,适合复杂条件;switch 更整齐,适合多值匹配。

两者没有绝对优劣,根据具体场景选择即可。


14. 一段综合示例

把本课的核心知识串到一个程序里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import "fmt"

func main() {
var score int
fmt.Print("请输入你的成绩(0-100):")
fmt.Scanln(&score)

// 使用 if 判断是否合法
if score < 0 || score > 100 {
fmt.Println("成绩不合法")
return
}

// 使用 switch 无表达式模式判断等级
switch {
case score >= 90:
fmt.Println("等级:优秀")
case score >= 80:
fmt.Println("等级:良好")
case score >= 70:
fmt.Println("等级:中等")
case score >= 60:
fmt.Println("等级:及格")
default:
fmt.Println("等级:不及格")
}

// 使用 if 初始化语句判断奇偶
if remainder := score % 2; remainder == 0 {
fmt.Printf("你的成绩 %d 是偶数\n", score)
} else {
fmt.Printf("你的成绩 %d 是奇数\n", score)
}

// 使用 switch 多值匹配判断特殊分数
switch score {
case 100:
fmt.Println("满分!")
case 0:
fmt.Println("零分...")
case 59:
fmt.Println("差一分及格,可惜了")
}
}

运行效果(输入 85):

1
2
3
请输入你的成绩(0-100):85
等级:良好
你的成绩 85 是奇数

运行效果(输入 59):

1
2
3
4
请输入你的成绩(0-100):59
等级:不及格
你的成绩 59 是奇数
差一分及格,可惜了

这个程序里同时用到了 ifif...elseif 初始化语句、无表达式 switch、多值匹配 switch,是本课内容的完整练习。


15. 常见坑总结

15.1 if 条件加了小括号

1
2
3
4
5
6
7
// 不推荐(Go 风格)
if (age >= 18) {
}

// 推荐
if age >= 18 {
}

虽然加括号不报错,但不符合 Go 惯例。


15.2 else 没有紧跟 }

1
2
3
4
5
6
7
// 错误:编译不通过
if age >= 18 {
fmt.Println("成年")
}
else {
fmt.Println("未成年")
}

else 必须和 } 在同一行。


15.3 if...else if 条件顺序写反了

把宽松条件放在前面,会导致精确条件永远无法执行。

1
2
3
4
5
6
// 错误示范
if score >= 60 {
fmt.Println("及格")
} else if score >= 90 {
fmt.Println("优秀") // 永远不会执行
}

条件从严格到宽松排列。


15.4 以为 Go 的 switch 需要 break

不需要。Go 的 switch 每个 case 自动结束。

如果你从其他语言转来,可能会本能地加 break。在 Go 里虽然加了不报错,但完全多余。


15.5 switchcase 后面写了重复值

1
2
3
4
5
6
switch day {
case 1:
fmt.Println("周一")
case 1: // 编译错误:重复的 case
fmt.Println("也是周一")
}

同一个 switch 里,case 的值不能重复。


15.6 fallthrough 的误用

fallthrough 是无条件穿透到下一个 case,它不检查下一个 case 的条件。如果你希望的是"满足某个条件时执行多个分支",fallthrough 可能不是正确的工具。


16. 本课练习

一定要亲手写,不要只看。

练习 1:判断正负零

要求:

  • 从控制台读取一个整数
  • 判断它是正数、负数还是零
  • if...else if...else 实现

练习 2:成绩等级判断

要求:

  • 从控制台读取一个成绩(0-100)
  • 用无表达式 switch 输出等级:优秀、良好、中等、及格、不及格
  • 加上输入合法性检查

练习 3:季节判断

要求:

  • 从控制台读取一个月份(1-12)
  • switch 多值匹配判断季节
    • 3, 4, 5 → 春季
    • 6, 7, 8 → 夏季
    • 9, 10, 11 → 秋季
    • 12, 1, 2 → 冬季
  • 非法月份输出提示

练习 4:体验 if 初始化语句

要求:

  • if 初始化语句的写法,在 if 里声明一个变量
  • ifelse 中都使用这个变量
  • 尝试在 if 语句块外面访问它,观察编译器报什么错

练习 5:简易菜单系统

要求:

  • 打印一个菜单(1: 查看余额 2: 存款 3: 取款 4: 退出)
  • 读取用户输入的选项
  • switch 根据选项输出不同的提示文字
  • 非法选项输出"无效操作"

练习 6:闰年判断

要求:

  • 从控制台读取一个年份
  • 判断它是否是闰年
  • 闰年规则:能被 4 整除但不能被 100 整除,或者能被 400 整除
  • if 结合逻辑运算符实现

提示:用 % 取余运算符。


17. 自测题

不看文档,试着回答:

17.1 概念题

  1. Go 的 if 条件表达式需要加小括号吗?
  2. else 为什么必须紧跟在 } 后面?
  3. if 初始化语句的作用是什么?它声明的变量作用域是什么?
  4. Go 的 switch 需要写 break 吗?为什么?
  5. fallthrough 是做什么的?它检查下一个 case 的条件吗?
  6. 一个 case 可以同时匹配多个值吗?怎么写?
  7. switch 后面不写表达式时,行为是什么?
  8. if...else if 的条件顺序为什么重要?
  9. &&|| 分别表示什么?
  10. 什么场景适合用 if,什么场景适合用 switch

如果你能流畅回答这些问题,说明这一课你已经真正理解了。


18. 本课总结

这一课你学到的,不只是两种语法结构,而是让程序具备"判断能力"的核心机制。

你现在应该已经理解:

  • if 条件不加括号,大括号不能省略,左大括号必须在同一行
  • else 必须紧跟 },不能换行
  • if 支持初始化语句,用分号和条件分隔,变量作用域限制在 if 块内
  • 逻辑运算符 &&||! 用于组合条件
  • switch 匹配到一个 case 后自动结束,不需要 break
  • switch 支持多值匹配,用逗号分隔
  • switch 不写表达式时,case 后面写布尔条件,等价于 if...else if
  • switch 也支持初始化语句
  • fallthrough 可以强制穿透,但很少使用

从这一课开始,你的程序已经不再是"直线执行"了,它可以根据不同的输入和状态做出不同的反应。


19. 下一课预告

下一课我们进入:循环结构 for

会重点讲:

  • Go 只有 for 一种循环关键字
  • 经典三段式 for 循环
  • 条件式 for(类似其他语言的 while
  • 无限循环
  • breakcontinue 的使用
  • for range 遍历(先做铺垫)
  • 循环中的常见坑

学完下一课,你就掌握了 Go 的全部基础控制流,可以写出有判断、有循环的完整逻辑了。