Go语言循环结构

Go语言中只有一种循环结构——for循环,但它足够灵活,可以替代其他语言中的while、do-while循环。另外,range关键字可以简便地遍历数组、切片、Map、字符串和通道等数据结构。本章将详细讲解for循环的各种形式以及控制循环的break、continue和标签语句。

设计哲学:Go语言刻意只保留一种循环关键字,避免语法冗余,同时通过“三部分可选”的机制覆盖所有循环场景。

1. 经典For循环(三段式)

语法结构:for 初始化语句; 条件表达式; 后置语句 { ... },其中三个部分都可以省略。

示例:打印0到4
package main

import "fmt"

func main() {
    for i := 0; i < 5; i++ {
        fmt.Println(i)
    }
}

执行流程:先执行i := 0;然后判断i < 5为true进入循环体;执行fmt.Println(i);最后执行i++,重复判断条件。

重要特性:

  • 初始化语句和后置语句都是可选的,但分号必须保留(除非省略条件本身)。
  • 初始化语句声明的变量作用域仅限于for块内部。
  • 条件表达式必须返回布尔值,不可使用整型1或0替代。

2. 条件循环(替代while)

省略初始化语句和后置语句,仅保留条件,即实现while循环的效果。

示例:累加到100
sum := 1
for sum < 100 {
    sum += sum
}
fmt.Println(sum) // 128

如果条件部分也省略,则成为无限循环(见后文)。

3. 无限循环

省略所有三个部分(连分号都不需要),形成无限循环。常用于服务器监听、事件循环等,通常配合break或return退出。

count := 0
for {
    count++
    if count > 3 {
        break
    }
    fmt.Println("循环中...")
}
// 输出: 循环中... (三次)
注意:无限循环没有显式退出条件时,会导致程序卡死。务必在循环体内设置break或return。

4. range 循环

range用于遍历数组、切片、字符串、map或通道。每次迭代返回两个值:索引(或键)和对应的元素值。

4.1 遍历数组/切片

nums := []int{10, 20, 30, 40}
for index, value := range nums {
    fmt.Printf("索引:%d 值:%d\n", index, value)
}
// 若只需索引:for index := range nums
// 若只需值:for _, value := range nums

4.2 遍历字符串(按Unicode码点)

message := "Go语言"
for i, ch := range message {
    fmt.Printf("%d: %c (类型: %T)\n", i, ch, ch) // ch 是 rune (int32)
}
// 输出:
// 0: G
// 1: o
// 2: 语
// 5: 言   (注意索引不连续,因为中文字符占用3个字节)

特别注意:range遍历字符串是按照UTF-8码点(rune)遍历,而非字节。如果只需要处理字节,可以使用for i := 0; i < len(s); i++。

4.3 遍历map

ages := map[string]int{
    "Alice": 30,
    "Bob":   25,
    "Carol": 28,
}
for name, age := range ages {
    fmt.Printf("%s 年龄 %d\n", name, age)
}
// 注意:map遍历顺序是随机的

4.4 遍历通道(channel)

ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    close(ch)
}()
for v := range ch {
    fmt.Println(v)
}
// 循环会在通道关闭并且所有元素被接收后结束

5. break 与 continue

  • break:立即终止最内层的for、switch或select语句,跳出循环。
  • continue:跳过本次循环体剩余语句,进入下一次迭代(只用于循环)。
示例:跳过偶数,遇到5则提前退出
for i := 1; i <= 10; i++ {
    if i%2 == 0 {
        continue  // 不打印偶数
    }
    if i == 5 {
        break     // 遇到5跳出循环
    }
    fmt.Print(i, " ")
}
// 输出: 1 3 

6. 标签(Label)与跳出多层循环

通过标签可以配合break或continue跳出/跳过任意层循环(并非只能针对最内层)。标签以labelName:的形式定义在循环语句上方。

示例:跳出嵌套的两层循环
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 5; j++ {
        if j == 2 {
            break outer   // 直接跳出外层循环
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// 输出: (0,0) (0,1) 

类似地,continue outer会跳过外层循环的本次迭代,直接进入外层循环的下一次迭代。

使用建议:标签名应当清晰易懂,避免过度使用导致代码混乱。但合理使用标签可以避免添加复杂标志变量。

7. 实用循环模式

7.1 遍历所有元素,但需要索引和值

items := []string{"a", "b", "c"}
for i, v := range items {
    // 使用 i 和 v
}

7.2 无限循环与select(并发场景)

for {
    select {
    case msg := <-ch:
        fmt.Println(msg)
    case <-time.After(5 * time.Second):
        fmt.Println("timeout")
        return
    }
}

7.3 倒序遍历

for i := len(arr) - 1; i >= 0; i-- {
    fmt.Println(arr[i])
}

7.4 循环修改切片中的元素(使用索引)

for i := range nums {
    nums[i] *= 2
}
// 注意:直接使用 value 修改不会影响原切片

综合示例:斐波那契数列生成器

循环综合运用
package main

import "fmt"

func main() {
    // 打印前10个斐波那契数
    a, b := 0, 1
    fmt.Print("斐波那契数列: ")
    for i := 0; i < 10; i++ {
        fmt.Print(a, " ")
        a, b = b, a+b
    }
    fmt.Println()

    // 使用range遍历字符串,统计中文字符
    str := "Hello, 世界"
    chineseCount := 0
    for _, ch := range str {
        if ch >= 0x4e00 && ch <= 0x9fff {
            chineseCount++
        }
    }
    fmt.Printf("字符串 '%s' 包含 %d 个中文字符\n", str, chineseCount)

    // 带标签的嵌套循环,找出二维数组中的第一个负数
    matrix := [][]int{
        {1, 2, 3},
        {4, -5, 6},
        {7, 8, 9},
    }
found:
    for i, row := range matrix {
        for j, val := range row {
            if val < 0 {
                fmt.Printf("找到负数 %d 位于 [%d][%d]\n", val, i, j)
                break found
            }
        }
    }
}
斐波那契数列: 0 1 1 2 3 5 8 13 21 34
字符串 'Hello, 世界' 包含 2 个中文字符
找到负数 -5 位于 [1][1]
小结:Go语言循环结构简洁而强大。掌握for和range的用法后,可以优雅地处理绝大多数迭代需求。下一章我们将学习函数与方法,包括多返回值、闭包、延迟调用等高级特性。