引入

很多操作要重复做——遍历数组、处理列表、重试请求。循环就是让一段代码反复执行的语法。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: cherry

range 返回两个值:索引值的副本

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: o

range 遍历字符串按 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 3

range 遍历 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,没有 whiledo-while
  • for 条件不需要加括号
  • range 返回的值是副本,修改 v 不影响原数据
  • 遍历 map 顺序不确定,需要有序的话自己排序
  • range 遍历 channel 会在 channel 关闭后自动结束
  • 字符串 range 按 rune 遍历,正确处理中文等多字节字符
  • 修改切片元素要用索引访问:slice[i] = newValue,不能改 rangev

理解

Go 把循环简化到只剩 for,但这一个关键字就覆盖了所有场景。range 是 Go 循环的精华,遍历各种数据结构都靠它,写起来比手动用索引安全多了。记住 range 返回的是值的副本这个细节,很多初学者的 bug 都出在这里。

引出

循环里有时候需要提前退出,有时候要跳过某次迭代。这就涉及到跳转语句了,看看 Go 跳转语句 怎么用 breakcontinuegoto 控制流程。