Go 编程实战系列(十九)
上一篇《Go switch 详解》中,我们学习了如何使用 switch 根据不同条件选择不同的代码分支。本篇继续学习 Go 流程控制中另一个非常重要的知识点:for 循环。
在实际开发中,经常需要让一段代码重复执行。例如:
输出 1~100
遍历用户列表
处理一批文件
读取数据库数据
遍历字符串
统计订单金额
重复执行某个任务
如果把相同代码写几十遍,不仅麻烦,而且难以维护。因此编程语言提供了循环结构。
Go 的循环设计非常简洁,只有一个核心关键字:
for
Go 没有单独的 while 和 do while,但通过不同形式的 for,可以实现传统 for、while、无限循环以及集合遍历等功能。
一、最基本的 for 循环
Go 中最经典的 for 写法是:
ini
for 初始化; 条件; 更新 {
循环代码
}
例如输出 1~5:
css
package main
import "fmt"
func main() {
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
}
输出:
1
2
3
4
5
这一段:
css
for i := 1; i <= 5; i++
可以分成三个部分:
css
i := 1
i <= 5
i++
分别表示:
初始化
循环条件
循环后的更新操作
二、for 循环执行流程
来看:
css
for i := 1; i <= 3; i++ {
fmt.Println(i)
}
程序首先执行:
css
i := 1
然后判断:
css
i <= 3
条件成立,执行:
css
fmt.Println(i)
之后执行:
css
i++
此时:
ini
i = 2
再次判断:
css
i <= 3
直到:
ini
i = 4
条件变成:
arduino
false
循环结束。
整个流程可以理解为:
初始化
↓
判断条件
↓
条件成立
↓
执行循环代码
↓
更新变量
↓
再次判断
三、循环变量从 0 开始
编程中非常常见的写法:
css
for i := 0; i < 10; i++ {
fmt.Println(i)
}
输出:
0
1
2
3
4
5
6
7
8
9
为什么经常从 0 开始?
因为 Go 中:
数组
切片
字符串
的索引都是从:
0
开始。
例如:
go
names := []string{"Tom", "Jack", "Lucy"}
索引:
css
names[0] = Tom
names[1] = Jack
names[2] = Lucy
所以以后会大量看到:
css
for i := 0; i < len(names); i++ {
}
四、使用 for 遍历切片
例如:
go
package main
import "fmt"
func main() {
names := []string{
"Tom",
"Jack",
"Lucy",
}
for i := 0; i < len(names); i++ {
fmt.Println(names[i])
}
}
输出:
Tom
Jack
Lucy
这里:
scss
len(names)
获取切片长度。
然后:
css
names[i]
根据索引取得元素。
五、倒序循环
for 不一定只能递增,也可以递减。
例如:
css
for i := 10; i >= 1; i-- {
fmt.Println(i)
}
输出:
10
9
8
7
6
5
4
3
2
1
这种写法经常用于:
倒计时
反向遍历
从后向前删除数据
六、一次增加多个数字
循环不一定每次:
css
i++
也可以:
css
i += 2
例如输出偶数:
css
for i := 0; i <= 10; i += 2 {
fmt.Println(i)
}
输出:
0
2
4
6
8
10
例如:
css
for i := 10; i <= 100; i += 10 {
fmt.Println(i)
}
每次增加 10。
七、Go 的 while 风格循环
Go 没有:
arduino
while
关键字。
但是可以使用:
for 条件 {
}
实现相同效果。
例如:
css
count := 1
for count <= 5 {
fmt.Println(count)
count++
}
输出:
1
2
3
4
5
这基本等价于其他语言中的:
arduino
while count <= 5
因此学习 Go 时可以记住:
arduino
Go 的 for 同时承担 for 和 while 的作用
八、无限循环
如果 for 后面什么都不写:
for {
}
就表示无限循环。
例如:
arduino
for {
fmt.Println("程序运行中")
}
程序会一直执行:
程序运行中
程序运行中
程序运行中
......
这就是:
死循环
但无限循环并不一定是错误。
例如服务器程序通常需要:
启动
↓
等待请求
↓
处理请求
↓
继续等待
很多长期运行的程序内部都会存在类似无限循环。
九、使用 break 结束循环
无限循环通常需要一个退出条件。
例如:
markdown
count := 1
for {
fmt.Println(count)
if count == 5 {
break
}
count++
}
输出:
1
2
3
4
5
当:
ini
count == 5
执行:
kotlin
break
循环立即结束。
break 下一篇会详细介绍,这里先理解它可以结束循环即可。
十、使用 continue 跳过本次循环
例如输出 1~10,但是不输出 5:
css
for i := 1; i <= 10; i++ {
if i == 5 {
continue
}
fmt.Println(i)
}
输出:
1
2
3
4
6
7
8
9
10
当:
ini
i == 5
执行:
kotlin
continue
本次循环剩余代码被跳过,然后继续下一次循环。
十一、for range
Go 提供了一种非常常用的遍历方式:
go
for range
例如:
go
names := []string{
"Tom",
"Jack",
"Lucy",
}
for index, value := range names {
fmt.Println(index, value)
}
输出:
0 Tom
1 Jack
2 Lucy
这里:
diff
index
表示索引。
value
表示当前元素。
这种写法比:
css
for i := 0; i < len(names); i++ {
fmt.Println(i, names[i])
}
更加简洁。
十二、只需要值怎么办?
有时候我们不关心索引,只需要元素。
可以使用:
go
for _, value := range names {
fmt.Println(value)
}
这里:
_
叫作空白标识符。
表示:
这个返回值我不需要
输出:
Tom
Jack
Lucy
这是 Go 中非常常见的写法。
十三、只需要索引
如果只需要索引,可以写:
go
for index := range names {
fmt.Println(index)
}
输出:
0
1
2
没有必要写:
go
for index, _ := range names {
}
通常直接:
go
for index := range names {
}
更加简洁。
十四、range 遍历字符串
range 还可以遍历字符串。
例如:
arduino
text := "Hello"
for index, char := range text {
fmt.Println(index, char)
}
这里:
arduino
char
得到的是:
go
rune
如果希望直接输出字符:
arduino
text := "Hello"
for index, char := range text {
fmt.Printf("%d %c\n", index, char)
}
输出:
0 H
1 e
2 l
3 l
4 o
十五、遍历中文字符串
range 在处理中文时非常重要。
例如:
arduino
text := "学习Go"
for index, char := range text {
fmt.Printf("%d %c\n", index, char)
}
输出类似:
0 学
3 习
6 G
7 o
为什么索引不是:
0
1
2
3
因为 Go 字符串使用 UTF-8 编码。
一个中文字符通常占多个字节。
range 会按照 Unicode 字符进行遍历,所以处理中文字符串时非常方便。
十六、for 循环求和
例如计算:
1 + 2 + 3 + ... + 100
代码:
css
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println(sum)
输出:
yaml
5050
这种:
bash
sum += i
叫作累加。
统计:
订单总金额
商品数量
访问次数
成绩总分
时经常使用。
十七、循环判断奇偶数
例如输出 1~20 中的偶数:
css
for i := 1; i <= 20; i++ {
if i%2 == 0 {
fmt.Println(i)
}
}
输出:
2
4
6
8
10
12
14
16
18
20
这里综合使用:
markdown
for
if
%
==
随着学习深入,我们会越来越多地组合前面学习过的语法。
十八、嵌套循环
for 中还可以继续写 for。
例如:
css
for i := 1; i <= 3; i++ {
for j := 1; j <= 3; j++ {
fmt.Println(i, j)
}
}
输出:
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
这种结构叫:
嵌套循环
常见于:
二维数据
表格
矩阵
组合计算
十九、九九乘法表
学习循环时经典案例就是九九乘法表。
css
package main
import "fmt"
func main() {
for i := 1; i <= 9; i++ {
for j := 1; j <= i; j++ {
fmt.Printf(
"%d×%d=%d\t",
j,
i,
i*j,
)
}
fmt.Println()
}
}
输出:
ini
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
......
这里使用了两层 for:
外层循环控制行
内层循环控制每一行内容
这是理解嵌套循环非常好的练习。
二十、遍历数据并查找目标
例如:
go
users := []string{
"Tom",
"Jack",
"Lucy",
"Bob",
}
for _, user := range users {
if user == "Lucy" {
fmt.Println("找到用户:", user)
break
}
}
输出:
找到用户: Lucy
找到目标后,没有必要继续遍历,所以直接:
kotlin
break
结束循环。
这种逻辑在实际开发中非常常见。
二十一、遍历订单计算总金额
例如:
go
prices := []int{
100,
200,
50,
300,
}
total := 0
for _, price := range prices {
total += price
}
fmt.Println("订单总金额:", total)
输出:
订单总金额: 650
这就是最基础的批量数据处理。
以后从数据库查询:
yaml
1000 条订单
也可能通过循环逐条进行:
统计
转换
过滤
处理
二十二、for 中变量作用域
例如:
css
for i := 0; i < 10; i++ {
fmt.Println(i)
}
这里:
css
i
是在 for 中定义的。
循环结束以后:
css
fmt.Println(i)
会报错。
因为 i 的作用域只在当前 for 语句中。
如果希望循环结束后继续使用:
css
i := 0
for ; i < 10; i++ {
fmt.Println(i)
}
fmt.Println("最终:", i)
此时可以正常访问。
二十三、避免死循环
初学者很容易写出:
css
i := 1
for i <= 10 {
fmt.Println(i)
}
这里忘记:
css
i++
所以:
css
i 永远等于 1
条件:
css
i <= 10
永远成立。
程序会不断输出:
1
1
1
1
......
因此使用条件循环时,一定要确认:
循环中的某些操作最终能够让条件变成 false。
二十四、不要在循环中进行不必要的重复操作
例如:
css
for i := 0; i < len(users); i++ {
// 数据处理
}
这种通常没有问题。
但如果循环内部存在非常耗时的操作:
go
for _, user := range users {
// 查询数据库
// 请求远程API
// 打开大文件
}
假设有:
10000 个用户
就可能执行:
10000 次数据库查询
造成严重性能问题。
实际开发中应该考虑:
批量查询
批量写入
缓存
并发处理
所以学习 for 不仅要知道怎么循环,还要逐渐建立:
循环次数 × 单次操作成本
这样的性能意识。
二十五、for 和 range 如何选择?
如果需要控制索引:
css
for i := 0; i < len(users); i++ {
}
非常合适。
如果只是遍历所有元素:
go
for _, user := range users {
}
通常更加简洁。
如果需要索引和值:
css
for i, user := range users {
}
如果需要无限循环:
for {
}
如果需要 while 风格:
rust
for condition {
}
Go 使用一个 for 就覆盖了这些不同场景。
二十六、完整实战案例
我们写一个简单的成绩统计程序:
go
package main
import "fmt"
func main() {
scores := []int{
85,
92,
76,
55,
88,
100,
}
total := 0
passCount := 0
for _, score := range scores {
total += score
if score >= 60 {
passCount++
}
}
average := float64(total) / float64(len(scores))
fmt.Println("学生人数:", len(scores))
fmt.Println("总分:", total)
fmt.Printf("平均分:%.2f\n", average)
fmt.Println("及格人数:", passCount)
}
输出:
学生人数: 6
总分: 496
平均分:82.67
及格人数: 5
这里已经综合使用:
diff
切片
for range
if
+=
++
类型转换
fmt.Printf
这也是学习编程非常重要的一点:
后面的知识并不是独立存在,而是不断组合前面学习过的语法解决实际问题。
二十七、常见错误
第一种是循环条件永远成立:
css
i := 0
for i < 10 {
fmt.Println(i)
}
忘记修改 i,会形成死循环。
第二种是数组越界:
css
users := []string{"Tom", "Jack"}
for i := 0; i <= len(users); i++ {
fmt.Println(users[i])
}
这里错误在:
scss
i <= len(users)
应该:
scss
i < len(users)
因为长度为 2 时,有效索引只有:
0
1
第三种是不需要索引却声明索引:
go
for index, value := range users {
fmt.Println(value)
}
如果 index 没有使用,Go 会报:
declared and not used
应该:
go
for _, value := range users {
fmt.Println(value)
}
二十八、本章小结
本篇详细介绍了 Go 中的 for 循环。
Go 虽然只有一个循环关键字,但使用方式非常灵活。
传统循环:
css
for i := 0; i < 10; i++ {
}
while 风格:
rust
for condition {
}
无限循环:
for {
}
遍历数据:
go
for index, value := range data {
}
只获取值:
go
for _, value := range data {
}
只获取索引:
go
for index := range data {
}
需要重点掌握:
- for 的初始化、条件和更新过程;
- 使用
i++、i--控制循环; - 使用
range遍历数据; - 使用
_忽略不需要的值; - 使用 for 遍历字符串;
- 使用嵌套 for 处理多层数据;
- 使用 break 提前结束循环;
- 使用 continue 跳过当前循环;
- 注意数组、切片索引越界;
- 注意避免意外死循环。
掌握 for 后,我们已经基本学习完 Go 最核心的流程控制结构:
arduino
if
switch
for
不过在循环和复杂流程控制中,还有三个非常重要的关键字需要单独学习:
kotlin
break
continue
goto
本章练习
- 使用 for 输出 1~100。
- 使用 for 输出 1~100 中所有偶数。
- 计算 1~100 的总和。
- 使用倒序循环输出 10~1。
- 使用
for range遍历一个字符串切片。 - 使用
range遍历"学习Go编程"。 - 编写九九乘法表。
- 遍历成绩列表,统计及格人数。
- 遍历商品价格并计算总金额。
- 编写一个无限循环,并在满足条件时使用 break 退出。
下一篇预告
《Go break、continue、goto 详解》
下一篇将继续学习 Go 流程控制中的三个关键字:
kotlin
break
continue
goto
主要包括:
-
break 结束循环;
-
break 结束 switch;
-
continue 跳过当前循环;
-
break 与 continue 的区别;
-
多层循环中的 break;
-
标签 Label;
-
带标签的 break;
-
带标签的 continue;
-
goto 基本语法;
-
goto 跳转规则;
-
goto 的实际使用场景;
-
为什么业务代码中应该谨慎使用 goto。
掌握这些流程控制关键字后,我们就可以更加灵活地控制 Go 程序的执行过程。