Go 编程实战系列(十八)
上一篇《Go if 判断详解》中,我们学习了如何使用 if、else if 和 else 根据不同条件控制程序执行流程。
如果只有两三个条件,使用 if 非常方便。但是当需要判断大量固定值时,如果一直使用 else if,代码就会变得越来越长。
例如:
lua
if status == 1 {
fmt.Println("待付款")
} else if status == 2 {
fmt.Println("待发货")
} else if status == 3 {
fmt.Println("已发货")
} else if status == 4 {
fmt.Println("已完成")
} else {
fmt.Println("未知状态")
}
这种场景使用 switch 会更加清晰。
Go 中的 switch 功能比很多语言更加灵活,不仅可以匹配固定值,还可以进行条件判断、多个值匹配以及类型判断。
本文主要介绍:
-
switch 基本语法
-
case 与 default
-
一个 case 匹配多个值
-
无表达式 switch
-
switch 与 if 的区别
-
break
-
fallthrough
-
类型 switch
-
switch 作用域
-
实际业务案例
一、switch 是什么?
switch 是 Go 中的一种流程控制语句。
它的主要作用是:
根据一个值或者条件,从多个分支中选择一个执行。
基本语法:
arduino
switch 表达式 {
case 值1:
执行代码
case 值2:
执行代码
default:
默认代码
}
例如:
go
package main
import "fmt"
func main() {
day := 1
switch day {
case 1:
fmt.Println("星期一")
case 2:
fmt.Println("星期二")
case 3:
fmt.Println("星期三")
default:
fmt.Println("未知")
}
}
输出:
星期一
程序会将:
sql
day
依次与不同 case 进行匹配。
因为:
ini
day == 1
所以执行:
arduino
case 1:
对应的代码。
二、case 分支
case 表示不同的匹配情况。
例如:
css
status := 2
switch status {
case 1:
fmt.Println("待付款")
case 2:
fmt.Println("待发货")
case 3:
fmt.Println("已发货")
case 4:
fmt.Println("已完成")
}
输出:
待发货
每一个 case 都可以包含多行代码:
css
switch status {
case 1:
fmt.Println("订单状态:待付款")
fmt.Println("请尽快完成付款")
case 2:
fmt.Println("订单状态:待发货")
fmt.Println("仓库正在处理")
}
三、default 默认分支
如果没有任何 case 匹配,可以使用:
arduino
default:
处理默认情况。
例如:
css
status := 100
switch status {
case 1:
fmt.Println("正常")
case 2:
fmt.Println("禁用")
default:
fmt.Println("未知状态")
}
输出:
未知状态
default 并不是必须存在的。
例如:
arduino
switch status {
case 1:
fmt.Println("正常")
case 2:
fmt.Println("禁用")
}
如果没有匹配项,程序直接继续执行 switch 后面的代码。
不过在业务状态判断中,增加 default 通常可以帮助我们发现异常数据。
四、字符串 switch
switch 不只能判断整数,也可以判断字符串。
例如:
go
role := "admin"
switch role {
case "admin":
fmt.Println("管理员")
case "user":
fmt.Println("普通用户")
case "guest":
fmt.Println("游客")
default:
fmt.Println("未知角色")
}
输出:
管理员
实际开发中非常常见,例如判断:
用户角色
请求类型
订单状态
文件类型
支付方式
环境名称
例如:
go
method := "GET"
switch method {
case "GET":
fmt.Println("查询数据")
case "POST":
fmt.Println("创建数据")
case "PUT":
fmt.Println("修改数据")
case "DELETE":
fmt.Println("删除数据")
}
五、一个 case 匹配多个值
Go 的 case 可以同时匹配多个值。
例如:
css
day := 6
switch day {
case 1, 2, 3, 4, 5:
fmt.Println("工作日")
case 6, 7:
fmt.Println("周末")
default:
fmt.Println("日期错误")
}
输出:
周末
多个值使用逗号分隔:
arduino
case 6, 7:
相当于:
ini
day == 6
或者
day == 7
再例如:
go
ext := ".jpg"
switch ext {
case ".jpg", ".jpeg", ".png", ".gif":
fmt.Println("图片文件")
case ".mp4", ".avi", ".mov":
fmt.Println("视频文件")
default:
fmt.Println("其他文件")
}
这种写法比多个 if || 更容易阅读。
六、Go switch 默认不需要 break
这是 Go 和 C、Java 等语言比较明显的区别。
在一些语言中:
arduino
case 执行完成
如果没有 break,可能继续执行下一个 case。
Go 默认不会这样。
例如:
dart
num := 1
switch num {
case 1:
fmt.Println("一")
case 2:
fmt.Println("二")
case 3:
fmt.Println("三")
}
只会输出:
一
不会继续执行:
二
三
因此 Go 中通常不需要:
kotlin
break
代码更加简洁。
七、switch 中的 break
虽然普通情况下不需要 break,但 Go 仍然支持它。
例如:
arduino
switch status {
case 1:
fmt.Println("开始处理")
if errorCondition {
break
}
fmt.Println("继续处理")
}
这里的 break 可以提前结束当前 switch。
不过普通的 case 分支没有必要这样写:
arduino
case 1:
fmt.Println("正常")
break
因为 case 执行完成后本来就会退出 switch。
八、无表达式 switch
Go 的 switch 有一个非常实用的功能:
可以不写表达式。
例如:
css
score := 85
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
输出:
良好
这种写法实际上类似:
arduino
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
无表达式 switch 特别适合多个条件区间判断。
九、switch 条件顺序
和 else if 一样,无表达式 switch 也需要注意条件顺序。
错误示例:
css
score := 95
switch {
case score >= 60:
fmt.Println("及格")
case score >= 90:
fmt.Println("优秀")
}
输出:
及格
因为:
score >= 60
已经成立,后面的条件不会继续判断。
正确:
arduino
switch {
case score >= 90:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
一般应该把范围更严格的条件放在前面。
十、switch 初始化语句
和 if 类似,switch 也可以包含初始化语句。
语法:
arduino
switch 初始化语句; 表达式 {
}
例如:
dart
switch num := 2; num {
case 1:
fmt.Println("一")
case 2:
fmt.Println("二")
default:
fmt.Println("其他")
}
输出:
二
这里的:
dart
num
只在当前 switch 作用域内有效。
switch 外面不能继续使用。
这种写法适合只需要临时使用一次的变量。
十一、fallthrough
Go 默认一个 case 执行完成后就退出 switch。
如果希望继续执行下一个 case,可以使用:
go
fallthrough
例如:
go
num := 1
switch num {
case 1:
fmt.Println("第一步")
fallthrough
case 2:
fmt.Println("第二步")
case 3:
fmt.Println("第三步")
}
输出:
第一步
第二步
需要特别注意:
fallthrough 不会判断下一个 case 的条件是否成立,而是直接执行下一个 case。
例如:
go
num := 1
switch num {
case 1:
fmt.Println("A")
fallthrough
case 100:
fmt.Println("B")
}
即使:
yaml
num != 100
仍然会输出:
css
A
B
因此 fallthrough 要谨慎使用。
实际 Go 项目中,它的使用频率并不高。
十二、switch 与逻辑运算
无表达式 switch 可以直接组合复杂条件。
例如:
go
age := 20
isLogin := true
switch {
case age < 18:
fmt.Println("年龄不足")
case !isLogin:
fmt.Println("请先登录")
case age >= 18 && isLogin:
fmt.Println("允许访问")
}
输出:
允许访问
这里使用了:
diff
<
!
>=
&&
等运算符。
十三、switch 判断月份
例如根据月份判断季节:
css
month := 8
switch month {
case 3, 4, 5:
fmt.Println("春季")
case 6, 7, 8:
fmt.Println("夏季")
case 9, 10, 11:
fmt.Println("秋季")
case 12, 1, 2:
fmt.Println("冬季")
default:
fmt.Println("月份错误")
}
输出:
夏季
这种固定值分类非常适合 switch。
十四、switch 判断订单状态
实际 Web 项目中,经常会遇到状态码。
例如:
css
status := 2
switch status {
case 0:
fmt.Println("订单已取消")
case 1:
fmt.Println("等待付款")
case 2:
fmt.Println("等待发货")
case 3:
fmt.Println("运输中")
case 4:
fmt.Println("交易完成")
default:
fmt.Println("未知订单状态")
}
相比大量:
ini
if status == 0 {
} else if status == 1 {
} else if status == 2 {
}
switch 明显更加清晰。
十五、switch 判断 HTTP 状态码
例如:
css
statusCode := 404
switch statusCode {
case 200:
fmt.Println("请求成功")
case 400:
fmt.Println("请求参数错误")
case 401:
fmt.Println("未登录")
case 403:
fmt.Println("没有权限")
case 404:
fmt.Println("资源不存在")
case 500:
fmt.Println("服务器错误")
default:
fmt.Println("其他状态")
}
输出:
资源不存在
这也是实际后端开发中非常典型的使用场景。
十六、类型 switch
Go 的 switch 还有一种特殊形式:
go
switch value := x.(type) {
}
叫作:
Type Switch,类型 switch。
它通常配合接口使用。
例如:
go
package main
import "fmt"
func printType(value any) {
switch v := value.(type) {
case int:
fmt.Println("整数:", v)
case string:
fmt.Println("字符串:", v)
case bool:
fmt.Println("布尔值:", v)
case float64:
fmt.Println("浮点数:", v)
default:
fmt.Println("其他类型")
}
}
func main() {
printType(100)
printType("Go")
printType(true)
}
输出:
arduino
整数: 100
字符串: Go
布尔值: true
这里:
bash
value.(type)
用于判断接口中实际保存的数据类型。
类型 switch 在以后学习:
typescript
interface
any
JSON
反射
时会经常遇到。
目前先理解基本用法即可。
十七、switch 和 if 如何选择?
这是实际开发中经常遇到的问题。
如果判断的是几个复杂条件:
scss
if age >= 18 && isLogin && !disabled {
}
通常使用:
if
更加自然。
如果判断的是同一个变量的多个固定值:
ini
status = 1
status = 2
status = 3
status = 4
通常使用:
arduino
switch
更加清晰。
例如:
arduino
switch status {
case 1:
case 2:
case 3:
}
如果是多个范围:
score >= 90
score >= 80
score >= 60
可以使用:
arduino
switch {
case score >= 90:
case score >= 80:
case score >= 60:
}
也可以使用 if else。
最终应该以代码可读性为主要判断标准。
十八、避免过长 switch
虽然 switch 很方便,但也不要把所有业务逻辑都塞进去。
不推荐:
arduino
switch status {
case 1:
// 几十行业务代码
case 2:
// 几十行业务代码
case 3:
// 几十行业务代码
}
可以拆成函数:
arduino
switch status {
case 1:
handlePending()
case 2:
handlePaid()
case 3:
handleFinished()
}
这样 switch 只负责:
选择业务分支
具体逻辑交给对应函数处理,代码更加容易维护。
十九、完整实战案例
我们写一个简单的用户权限判断程序:
go
package main
import "fmt"
func checkRole(role string) {
switch role {
case "admin":
fmt.Println("管理员")
fmt.Println("拥有全部权限")
case "manager":
fmt.Println("经理")
fmt.Println("拥有管理权限")
case "user":
fmt.Println("普通用户")
fmt.Println("拥有基础权限")
case "guest":
fmt.Println("游客")
fmt.Println("只能浏览")
default:
fmt.Println("未知用户角色")
}
}
func main() {
checkRole("admin")
}
输出:
管理员
拥有全部权限
以后开发后台管理系统时,经常会遇到类似逻辑。
不过真正的权限系统通常不会直接使用 switch 写死权限,而会使用:
数据库
RBAC
Casbin
权限表
等方式进行管理。
二十、常见错误
1. 忘记 case 后面的冒号
错误:
arduino
switch status {
case 1
fmt.Println("正常")
}
正确:
arduino
switch status {
case 1:
fmt.Println("正常")
}
2. 误以为必须写 break
没有必要:
arduino
switch status {
case 1:
fmt.Println("正常")
break
}
直接:
arduino
switch status {
case 1:
fmt.Println("正常")
}
即可。
3. 滥用 fallthrough
例如:
go
case 1:
fallthrough
case 2:
fallthrough
case 3:
这种代码很容易产生难以理解的执行流程。
除非确实需要,否则尽量不要使用 fallthrough。
4. 条件顺序错误
例如:
arduino
switch {
case score >= 60:
fmt.Println("及格")
case score >= 90:
fmt.Println("优秀")
}
应该把更严格的条件放前面。
二十一、switch 最佳实践
实际 Go 项目中可以遵循几个原则。
第一,同一个变量存在多个固定值时优先考虑 switch:
arduino
switch status {
case 1:
case 2:
case 3:
}
第二,多个 case 执行相同逻辑时直接合并:
arduino
case 1, 2, 3:
第三,业务状态尽量处理 default:
arduino
default:
return fmt.Errorf("未知状态:%d", status)
第四,不要为了使用 switch 而使用 switch。简单条件:
if age >= 18 {
}
通常没有必要改成:
arduino
switch {
case age >= 18:
}
第五,尽量少使用 fallthrough,保持每个 case 独立、明确。
二十二、本章小结
本篇详细介绍了 Go 中的 switch 条件判断。
需要重点掌握:
go
switch
case
default
fallthrough
Type Switch
核心知识包括:
- switch 可以匹配整数、字符串等值;
- 一个 case 可以同时匹配多个值;
- Go 的 case 默认执行完成后自动退出;
- 普通情况下不需要写 break;
- switch 可以不写表达式;
- 无表达式 switch 可以替代部分 else if;
- fallthrough 会强制执行下一个 case;
- 类型 switch 可以判断接口中的实际类型;
- 固定状态判断通常使用 switch 更清晰。
学习完 if 和 switch 后,我们已经能够控制程序"选择执行哪一段代码"。
接下来还有另外一种非常重要的流程控制:
让一段代码重复执行。
这就是循环。
本章练习
- 使用 switch 根据数字 1~7 输出星期。
- 根据月份判断春夏秋冬。
- 根据订单状态码输出订单状态。
- 使用一个 case 同时匹配 JPG、PNG、GIF 图片格式。
- 使用无表达式 switch 判断成绩等级。
- 使用 switch 判断 HTTP 状态码。
- 编写一个用户角色判断程序。
- 使用 Type Switch 判断 int、string、bool 和 float64。
- 尝试使用 fallthrough,观察下一个 case 是否判断条件。
下一篇预告
《Go for 循环详解》
下一篇将学习 Go 中最重要的循环语句 for,包括:
-
for 基本语法;
-
传统三段式 for;
-
while 风格循环;
-
无限循环;
-
break;
-
continue;
-
range;
-
遍历字符串;
-
遍历数组和切片;
-
嵌套循环;
-
循环标签;
-
常见死循环问题;
-
实际开发中的批量数据处理案例。
Go 没有单独的 while 和 do while,一个 for 就可以完成几乎所有循环任务。