引入
很多操作要重复做——遍历数组、处理列表、重试请求。循环就是让一段代码反复执行的语法。Go 在这方面做得很极端:只有 for 一种循环语句,没有 while,没有 do-while,所有循环都用 for 搞定。
正文
定义
定义
循环是让一段代码重复执行的语法结构。Go 只有
for一种循环关键字,通过不同的写法实现计数循环、条件循环和无限循环。配合range可以方便地遍历切片、map、字符串等数据结构。
语法
// 经典 for 循环
for i := 0; i < 10; i++ {
fmt.Println(i)
}
// 条件循环(相当于 while)
for condition {
// condition 为 true 时一直执行
}
// 无限循环
for {
// 永远执行
}
// range 遍历
for index, value := range slice {
fmt.Println(index, value)
}例子
经典计数循环
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 输出:0 1 2 3 4和 C 语言类似,三个部分:初始化、条件、后置操作。但不需要加括号。
条件循环(while 风格)
n := 10
for n > 0 {
fmt.Println(n)
n--
}只写条件,不写初始化和后置操作,就是 while 的效果。
无限循环
for {
input := readInput()
if input == "quit" {
break
}
process(input)
}省略条件就是无限循环,配合 break 在合适时机退出。服务器主循环、事件监听都常用这种写法。
range 遍历切片
fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
fmt.Printf("%d: %s\n", i, fruit)
}
// 0: apple
// 1: banana
// 2: cherryrange 返回两个值:索引和值的副本。
range 只要索引
nums := []int{10, 20, 30}
for i := range nums {
fmt.Println(i)
}
// 0 1 2只写一个变量,拿到的是索引。
range 只要值
nums := []int{10, 20, 30}
for _, v := range nums {
fmt.Println(v)
}
// 10 20 30用 _ 忽略索引,只要值。这是最常见的写法。
range 遍历 map
scores := map[string]int{
"Alice": 95,
"Bob": 87,
"Carol": 92,
}
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}range 遍历 map 返回 key 和 value。注意 map 的遍历顺序是随机的,不保证顺序。
range 遍历字符串
for i, r := range "你好Go" {
fmt.Printf("%d: %c\n", i, r)
}
// 0: 你
// 3: 好
// 6: G
// 7: orange 遍历字符串按 Unicode 字符(rune)遍历,索引是字节位置。
range 遍历 channel
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for v := range ch {
fmt.Println(v)
}
// 1 2 3range 遍历 channel 会一直读取,直到 channel 被关闭。
常见写法
| 写法 | 说明 |
|---|---|
for i := 0; i < n; i++ | 计数循环 |
for condition | 条件循环(while 风格) |
for { } | 无限循环 |
for i, v := range slice | 遍历切片 |
for _, v := range slice | 只要值,忽略索引 |
for k, v := range map | 遍历 map |
for i, r := range str | 遍历字符串字符 |
特点
- Go 只有
for,没有while和do-while for条件不需要加括号range返回的值是副本,修改v不影响原数据- 遍历 map 顺序不确定,需要有序的话自己排序
range遍历 channel 会在 channel 关闭后自动结束- 字符串
range按 rune 遍历,正确处理中文等多字节字符 - 修改切片元素要用索引访问:
slice[i] = newValue,不能改range的v
理解
Go 把循环简化到只剩 for,但这一个关键字就覆盖了所有场景。range 是 Go 循环的精华,遍历各种数据结构都靠它,写起来比手动用索引安全多了。记住 range 返回的是值的副本这个细节,很多初学者的 bug 都出在这里。
引出
循环里有时候需要提前退出,有时候要跳过某次迭代。这就涉及到跳转语句了,看看 Go 跳转语句 怎么用 break、continue、goto 控制流程。