Go 编程实战系列(二十)
上一篇《Go for 循环详解》中,我们学习了 Go 中唯一的循环关键字 for,包括普通循环、无限循环、range 遍历以及嵌套循环等内容。本篇继续学习流程控制中的三个重要关键字:
kotlin
break
continue
goto
它们都可以改变程序原本的执行流程,但作用完全不同。简单来说:
arduino
break 结束循环或 switch
continue 跳过当前循环,继续下一次
goto 直接跳转到指定标签
在实际 Go 项目中,break 和 continue 使用非常普遍,而 goto 使用频率相对较低。掌握三者的区别,可以帮助我们写出更加清晰的流程控制代码。
一、break 是什么?
break 的作用可以理解为:
立即结束当前循环或 switch。
例如:
css
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
}
输出:
1
2
3
4
当:
ini
i == 5
条件成立后执行:
kotlin
break
整个 for 循环立即结束,因此:
5
6
7
8
9
10
都不会继续处理。
二、break 与普通循环结束的区别
普通循环:
css
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
是因为:
css
i <= 5
最终变成 false,所以循环自然结束。
而:
kotlin
break
是不管循环条件是否仍然成立,都直接结束循环。
例如:
css
for i := 1; i <= 10000; i++ {
if i == 10 {
break
}
fmt.Println(i)
}
虽然原本可以循环到 10000,但是运行到 10 时就直接退出。
三、break 查找数据
实际开发中,break 很适合用于查找数据。
例如:
go
users := []string{
"Tom",
"Jack",
"Lucy",
"Bob",
}
for _, user := range users {
if user == "Lucy" {
fmt.Println("找到用户:", user)
break
}
}
输出:
找到用户: Lucy
找到 Lucy 后,就没有必要继续检查 Bob,因此使用 break 提前结束循环。
如果有 10 万条数据,而目标在第 100 条就已经找到,那么继续遍历剩余数据通常没有意义。
四、break 结束无限循环
上一篇学习过:
for {
}
表示无限循环。
无限循环经常配合 break 使用:
markdown
count := 1
for {
fmt.Println(count)
if count >= 5 {
break
}
count++
}
输出:
1
2
3
4
5
这种:
kotlin
for {
if 条件 {
break
}
}
在命令行工具、任务处理等场景中非常常见。
五、break 与 switch
break 不仅可以结束 for,也可以结束 switch。
不过上一篇已经学习过,Go 的 switch 默认执行完一个 case 后就会自动退出,因此普通情况下不需要手动写:
kotlin
break
例如:
css
status := 1
switch status {
case 1:
fmt.Println("待付款")
case 2:
fmt.Println("已付款")
}
当 case 1 执行完成后,不会自动进入 case 2。
所以没有必要:
arduino
case 1:
fmt.Println("待付款")
break
但是在 case 内部需要提前退出 switch 时,可以主动使用 break。
六、continue 是什么?
continue 与 break 很容易混淆。
break:
整个循环结束
continue:
只结束当前这一轮
然后继续下一轮循环
例如:
css
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println(i)
}
输出:
1
2
4
5
当:
ini
i == 3
执行 continue 后:
css
fmt.Println(i)
不会执行。
但是整个循环并没有结束,而是继续处理:
4
5
七、break 与 continue 的区别
来看两个例子。
使用 break:
css
for i := 1; i <= 5; i++ {
if i == 3 {
break
}
fmt.Println(i)
}
输出:
1
2
使用 continue:
css
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println(i)
}
输出:
1
2
4
5
因此可以简单记住:
kotlin
break
↓
后面的循环全部不要了
continue
↓
当前这一次不要了
后面的循环继续
八、continue 过滤数据
continue 在实际项目中非常适合过滤不需要处理的数据。
例如:
go
scores := []int{
90,
55,
80,
40,
100,
}
for _, score := range scores {
if score < 60 {
continue
}
fmt.Println("及格成绩:", score)
}
输出:
及格成绩: 90
及格成绩: 80
及格成绩: 100
遇到不及格的数据:
score < 60
直接跳过。
这种写法通常比:
arduino
if score >= 60 {
// 大量业务代码
}
更容易减少代码嵌套。
九、continue 处理无效数据
例如我们有一批用户:
go
users := []string{
"Tom",
"",
"Jack",
"",
"Lucy",
}
空字符串表示无效用户。
可以:
sql
for _, user := range users {
if user == "" {
continue
}
fmt.Println("处理用户:", user)
}
输出:
处理用户: Tom
处理用户: Jack
处理用户: Lucy
实际项目中经常使用类似结构:
go
for _, item := range items {
if item 无效 {
continue
}
处理 item
}
也就是先排除不需要的数据,再处理正常数据。
十、continue 使用时需要注意什么?
下面这种 while 风格循环要特别小心:
css
i := 0
for i < 10 {
if i == 5 {
continue
}
i++
}
当:
ini
i == 5
执行 continue 后:
css
i++
不会执行。
于是 i 永远等于 5,程序形成死循环。
应该保证循环变量能够正常更新,例如:
css
i := 0
for i < 10 {
i++
if i == 5 {
continue
}
fmt.Println(i)
}
所以使用 continue 时,一定要注意是否跳过了关键的状态更新代码。
十一、嵌套循环中的 break
例如:
css
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
if j == 2 {
break
}
fmt.Println(i, j)
}
}
输出:
1 1
2 1
3 1
这里需要注意:
kotlin
break
默认只结束:
当前所在的最内层循环。
也就是说,它只结束:
css
for j := 1; j <= 3; j++ {
}
外层:
css
for i := 1; i <= 3; i++ {
}
仍然会继续执行。
十二、如何一次退出多层循环?
假设:
css
for i := 1; i <= 10; i++ {
for j := 1; j <= 10; j++ {
if i == 3 && j == 3 {
// 希望整个循环全部结束
}
}
}
普通 break 只能退出内层循环。
Go 可以使用:
css
Label
也就是标签。
例如:
css
Outer:
for i := 1; i <= 10; i++ {
for j := 1; j <= 10; j++ {
if i == 3 && j == 3 {
break Outer
}
fmt.Println(i, j)
}
}
这里:
makefile
Outer:
定义一个标签。
然后:
kotlin
break Outer
直接结束标签对应的循环。
这种方式在复杂嵌套循环中比较有用。
十三、带标签的 continue
continue 也可以配合标签。
例如:
css
Outer:
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
if j == 2 {
continue Outer
}
fmt.Println(i, j)
}
}
当:
ini
j == 2
执行:
kotlin
continue Outer
会直接进入外层循环的下一次迭代。
需要注意,带标签的 continue 比普通 continue 更难阅读,因此没有明确需求时不要滥用。
十四、goto 是什么?
除了:
kotlin
break
continue
Go 还保留了一个比较特殊的流程控制关键字:
arduino
goto
goto 的作用是:
无条件跳转到当前函数中的指定标签。
基本语法:
arduino
goto 标签名称
标签名称:
代码
例如:
go
package main
import "fmt"
func main() {
fmt.Println("第一步")
goto End
fmt.Println("第二步")
End:
fmt.Println("程序结束")
}
输出:
第一步
程序结束
这行:
arduino
fmt.Println("第二步")
没有执行。
因为:
vbnet
goto End
直接跳到了:
makefile
End:
的位置。
十五、goto 标签
标签的基本形式:
makefile
标签名称:
例如:
makefile
Start:
或者:
makefile
End:
然后通过:
arduino
goto Start
进行跳转。
例如:
dart
num := 1
Start:
fmt.Println(num)
num++
if num <= 5 {
goto Start
}
输出:
1
2
3
4
5
从功能上看,这相当于实现了一个循环。
但是这种写法并不推荐。
更清晰的方式应该是:
dart
for num := 1; num <= 5; num++ {
fmt.Println(num)
}
十六、为什么不推荐大量使用 goto?
goto 最大的问题不是不能用,而是:
使用过多会让程序执行流程变得难以理解。
例如:
less
goto A
B:
// 一些代码
goto C
A:
// 一些代码
goto B
C:
// 一些代码
代码执行顺序不再是简单的从上到下。
阅读代码时需要不断寻找:
css
A 在哪里?
B 在哪里?
C 又在哪里?
如果项目越来越大,这种代码维护成本会非常高。
因此普通业务逻辑优先使用:
kotlin
if
switch
for
break
continue
return
而不是大量使用 goto。
十七、goto 不能随意跳过变量声明
Go 对 goto 有一定限制。
例如不能通过 goto 跳过变量声明,然后跳到该变量已经进入作用域的位置。
类似下面这种代码会产生编译错误:
vbnet
goto End
name := "Tom"
End:
fmt.Println(name)
因为跳转直接绕过了:
go
name := "Tom"
Go 会限制这种可能导致变量状态不明确的跳转。
因此 goto 并不是可以在函数中毫无限制地随意跳转。
十八、goto 的适用场景
虽然日常业务开发不建议大量使用 goto,但并不代表它完全没有价值。
例如复杂的多层资源处理:
步骤1
↓
步骤2
↓
步骤3
↓
发生错误
↓
统一清理
某些底层代码可能使用 goto 跳转到统一处理位置。
例如概念上:
vbnet
if err1 != nil {
goto Error
}
if err2 != nil {
goto Error
}
if err3 != nil {
goto Error
}
return
Error:
fmt.Println("处理错误")
不过在现代 Go 代码中,更多时候会使用:
go
defer
完成资源清理。
例如:
go
file, err := os.Open("data.txt")
if err != nil {
return
}
defer file.Close()
因此以后学习 defer 后,会发现大多数资源释放问题并不需要 goto。
十九、break、continue、return、goto 的区别
这几个关键字很容易混淆,可以简单总结:
kotlin
break
结束循环或 switch
continue
跳过当前循环,继续下一轮
return
结束当前函数
goto
跳转到当前函数中的指定标签
例如:
go
for _, user := range users {
if user == "" {
continue
}
if user == "admin" {
break
}
}
这里:
kotlin
continue
遇到空用户只是跳过。
而:
kotlin
break
遇到 admin 后直接结束整个循环。
如果改成:
kotlin
return
那么结束的不只是循环,而是当前整个函数。
二十、实际案例:查找第一个符合条件的数据
例如:
go
numbers := []int{
3,
7,
11,
20,
30,
}
for _, num := range numbers {
if num%2 == 0 {
fmt.Println("找到第一个偶数:", num)
break
}
}
输出:
找到第一个偶数: 20
找到目标后立即结束循环,可以避免不必要的遍历。
二十一、实际案例:过滤无效订单
例如:
go
orders := []int{
100,
0,
200,
-1,
300,
}
total := 0
for _, price := range orders {
if price <= 0 {
continue
}
total += price
}
fmt.Println("有效订单总金额:", total)
输出:
有效订单总金额: 600
这里:
kotlin
if price <= 0 {
continue
}
负责过滤无效数据。
剩下的正常数据才执行:
makefile
total += price
这是一种非常常见的 Go 代码结构。
二十二、实际案例:用户数据处理
例如:
go
package main
import "fmt"
func main() {
users := []string{
"Tom",
"",
"Jack",
"admin",
"Lucy",
}
for _, user := range users {
if user == "" {
continue
}
if user == "admin" {
fmt.Println("发现管理员,停止处理")
break
}
fmt.Println("处理用户:", user)
}
}
输出:
处理用户: Tom
处理用户: Jack
发现管理员,停止处理
这里同时使用了:
kotlin
continue
break
逻辑非常清楚:
无效数据
↓
跳过
普通用户
↓
正常处理
发现管理员
↓
结束循环
二十三、什么时候使用 break?
适合以下情况:
已经找到目标数据
已经满足退出条件
用户主动选择退出
发生某种情况后无需继续遍历
需要结束无限循环
例如:
kotlin
if found {
break
}
二十四、什么时候使用 continue?
适合:
当前数据无效
当前数据不需要处理
过滤特定数据
跳过错误数据
只处理符合条件的数据
例如:
go
for _, user := range users {
if !user.Enabled {
continue
}
process(user)
}
这种代码在实际项目中非常常见。
二十五、什么时候使用 goto?
goto 应该更加谨慎。
普通业务逻辑中,如果可以使用:
go
if
for
switch
return
defer
清晰解决,就没有必要使用 goto。
如果确实存在复杂的多层流程跳转,而且使用 goto 能明显让代码更加简单,可以考虑。
判断标准不是:
goto 能不能用?
而应该是:
使用 goto 后,代码是不是更容易理解和维护?
如果答案是否定的,就不要使用。
二十六、常见错误
第一种错误是把 break 和 continue 混淆。
例如:
go
for _, user := range users {
if user == "" {
break
}
}
如果只是想跳过空数据,这里应该使用:
kotlin
continue
否则遇到第一个空数据后,后面的所有用户都不会处理。
第二种错误是在 while 风格循环中因为 continue 导致循环变量无法更新:
css
i := 0
for i < 10 {
if i == 5 {
continue
}
i++
}
会形成死循环。
第三种错误是大量使用 goto:
arduino
goto A
goto B
goto C
虽然某些情况下可以运行,但会明显降低代码可读性。
第四种错误是在嵌套循环中误以为普通 break 会结束所有循环。实际上:
kotlin
break
默认只退出最内层循环。
如果需要退出指定外层循环,可以考虑标签:
kotlin
Outer:
for {
for {
break Outer
}
}
二十七、本章小结
本篇介绍了 Go 中三个重要的流程控制关键字:
kotlin
break
continue
goto
其中 break 用于:
arduino
结束 for
结束 switch
提前停止数据查找
退出无限循环
continue 用于:
跳过当前循环
继续下一轮
过滤无效数据
减少代码嵌套
goto 用于:
跳转到指定标签
还学习了:
css
Label 标签
break Label
continue Label
goto Label
需要重点记住:
break是结束整个当前循环;continue是跳过当前这一轮;- 嵌套循环中的普通
break只结束最内层循环; - 标签可以控制指定的外层循环;
goto可以进行无条件跳转;- 普通业务开发不要滥用
goto; - Go 中通常优先使用
if、for、return、defer等结构保持代码清晰。
到这里,Go 基础流程控制部分已经基本学习完成。接下来我们开始进入非常重要的数据结构部分。
本章练习
- 使用 for 输出 1~100,在数字等于 50 时使用
break结束循环。 - 输出 1~20,使用
continue跳过所有偶数。 - 遍历用户列表,遇到空用户名时跳过。
- 遍历商品列表,找到目标商品后使用
break。 - 编写一个无限循环,在计数达到 10 时退出。
- 编写两层循环,观察普通
break会结束哪一层。 - 使用
break Label一次退出两层循环。 - 使用
continue Label观察外层循环的执行过程。 - 编写一个简单
goto示例,并尝试使用普通for重写。
下一篇预告
《Go 数组 Array 详解》
下一篇将正式进入 Go 数据结构的学习,首先介绍最基础的数组 Array,包括:
-
什么是数组;
-
数组的声明;
-
数组初始化;
-
自动推导数组长度;
-
根据索引访问元素;
-
修改数组元素;
-
数组长度
len; -
for 遍历数组;
-
for range 遍历数组;
-
多维数组;
-
数组比较;
-
数组作为函数参数;
-
Go 数组是值类型;
-
数组与切片的区别;
-
数组越界问题;
-
实际开发案例。
理解数组之后,下一步就可以学习 Go 中使用频率更高、也更加重要的数据结构------Slice 切片。