Go语言循环语句详解:for、range与循环控制

1. 引言

循环是编程语言中控制程序重复执行某段代码的核心结构。Go语言的设计哲学强调简洁与高效,因此在循环语句的设计上与其他语言(如C、Java、Python)有明显不同。Go语言只提供了一种循环结构:for循环。这种看似简单的设计,通过灵活的语法变体,足以覆盖所有常见的循环场景,包括传统的条件循环、无限循环以及遍历集合的迭代循环。

本文将全面解析Go语言中的循环语句,涵盖for循环的三种基本形式、用于遍历的range关键字,以及breakcontinuegoto等循环控制语句。通过丰富的代码示例,帮助你掌握Go语言循环的精髓。

2. for循环的三种基本形式

Go语言的for循环有三种写法,分别对应不同的使用场景。

2.1 基本for循环(类似C语言的for)

这种形式包含初始化语句、条件表达式和后续语句,用分号分隔。

go 复制代码
package main

import "fmt"

func main() {
    // 形式一:完整的for循环
    for i := 0; i < 5; i++ {
        fmt.Printf("i = %d\n", i)
    }
    // 输出:
    // i = 0
    // i = 1
    // i = 2
    // i = 3
    // i = 4
}

要点

  • 初始化语句(i := 0)仅在循环开始时执行一次。
  • 条件表达式(i < 5)在每次迭代前求值,为true则执行循环体。
  • 后续语句(i++)在每次循环体执行完毕后执行。
  • 初始化语句中声明的变量(如i)其作用域仅限于该for循环内部。

2.2 条件for循环(类似while循环)

省略初始化语句和后续语句,只保留条件表达式。此时for循环的功能等同于其他语言中的while循环。

go 复制代码
package main

import "fmt"

func main() {
    count := 5
    // 形式二:仅包含条件的for循环(类似while)
    for count > 0 {
        fmt.Printf("倒计时: %d\n", count)
        count--
    }
    fmt.Println("发射!")
}

2.3 无限循环

省略所有三个部分,形成一个无限循环。必须在循环体内使用break语句或其他方式退出,否则程序将永远运行。

go 复制代码
package main

import (
    "fmt"
    "time"
)

func main() {
    // 形式三:无限循环
    ticker := 0
    for {
        fmt.Printf("心跳 %d\n", ticker)
        ticker++
        time.Sleep(1 * time.Second) // 等待1秒

        // 退出条件
        if ticker >= 5 {
            fmt.Println("心跳停止")
            break
        }
    }
}

3. 使用range进行遍历

range关键字是Go语言中用于遍历数组、切片、字符串、映射(map)和通道(channel)的利器。它会在每次迭代中返回索引(或键)和值。

3.1 遍历数组和切片

go 复制代码
package main

import "fmt"

func main() {
    fruits := []string{"苹果", "香蕉", "橙子"}

    // 遍历切片,获取索引和值
    for index, fruit := range fruits {
        fmt.Printf("fruits[%d] = %s\n", index, fruit)
    }

    // 如果只需要值,用下划线忽略索引
    for _, fruit := range fruits {
        fmt.Printf("水果: %s\n", fruit)
    }

    // 如果只需要索引
    for index := range fruits {
        fmt.Printf("索引: %d\n", index)
    }
}

3.2 遍历字符串

遍历字符串时,range会迭代Unicode码点(rune),而不是字节。这对于处理中文等多字节字符非常安全。

go 复制代码
package main

import "fmt"

func main() {
    str := "Hello, 世界"

    // 遍历字符串,i是字节索引,c是rune(Unicode码点)
    for i, c := range str {
        fmt.Printf("str[%d] = %c (Unicode: %U)\n", i, c, c)
    }
}

3.3 遍历映射(map)

遍历map时,每次迭代的顺序是随机的(Go语言的故意设计,以防止开发者依赖特定顺序)。

go 复制代码
package main

import "fmt"

func main() {
    scoreMap := map[string]int{
        "Alice": 95,
        "Bob":   87,
        "Cathy": 92,
    }

    for name, score := range scoreMap {
        fmt.Printf("%s 的分数是 %d\n", name, score)
    }
}

3.4 遍历通道(channel)

range会从通道中持续接收值,直到通道被关闭。

go 复制代码
package main

import "fmt"

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch) // 必须关闭通道,range才能结束

    for value := range ch {
        fmt.Println("从通道接收到:", value)
    }
}

4. 循环控制语句

4.1 break语句

break用于立即终止最内层的forswitchselect语句的执行。

go 复制代码
package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        if i == 5 {
            fmt.Println("遇到5,提前终止循环")
            break
        }
        fmt.Println(i)
    }
}

带标签的break:可以终止外层循环。

go 复制代码
package main

import "fmt"

func main() {
OuterLoop:
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if i == 1 && j == 1 {
                fmt.Printf("在 i=%d, j=%d 处跳出外层循环\n", i, j)
                break OuterLoop
            }
            fmt.Printf("i=%d, j=%d\n", i, j)
        }
    }
}

4.2 continue语句

continue用于跳过当前循环的剩余语句,直接进入下一次迭代。

go 复制代码
package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        if i%2 == 0 { // 跳过偶数
            continue
        }
        fmt.Println("奇数:", i)
    }
}

同样,continue也可以使用标签来指定跳转到哪个循环的下一次迭代。

4.3 goto语句

goto语句可以将控制无条件转移到同一函数内的带标签语句。虽然在现代编程中不鼓励滥用,但在某些错误处理或跳出多层嵌套的场景下,它可以使代码更清晰。

go 复制代码
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        for j := 0; j < 3; j++ {
            if i == 1 && j == 1 {
                fmt.Println("跳到循环外")
                goto Exit
            }
            fmt.Printf("i=%d, j=%d\n", i, j)
        }
    }
Exit:
    fmt.Println("已通过goto跳出")
}

5. 嵌套循环与常见模式

5.1 嵌套循环示例

go 复制代码
package main

import "fmt"

func main() {
    // 打印九九乘法表
    for i := 1; i <= 9; i++ {
        for j := 1; j <= i; j++ {
            fmt.Printf("%d×%d=%-2d  ", j, i, i*j) // %-2d 表示左对齐,占2位
        }
        fmt.Println() // 换行
    }
}

5.2 使用循环处理切片(过滤、映射)

go 复制代码
package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // 过滤:找出偶数
    var evens []int
    for _, num := range numbers {
        if num%2 == 0 {
            evens = append(evens, num)
        }
    }
    fmt.Println("偶数:", evens)

    // 映射:将每个数平方
    squares := make([]int, len(numbers))
    for i, num := range numbers {
        squares[i] = num * num
    }
    fmt.Println("平方:", squares)
}

6. 性能注意事项与最佳实践

  1. 避免在循环内频繁分配内存 :例如,在循环内反复使用append时,如果可能,尽量预先分配好切片的容量。

    go 复制代码
    // 不佳
    var result []int
    for i := 0; i < 1000; i++ {
        result = append(result, i*2) // 可能导致多次重新分配
    }
    
    // 更佳
    result := make([]int, 0, 1000) // 预先分配容量
    for i := 0; i < 1000; i++ {
        result = append(result, i*2)
    }
  2. 遍历大切片时考虑使用索引 :如果不需要修改值,使用for i := 0; i < len(slice); i++for _, v := range slice在极少数性能敏感场景下可能略有优势,因为后者会产生值的副本。但通常可读性优先,差异可忽略。

  3. 小心循环变量的捕获(在闭包中):在Go 1.22版本之前,在goroutine或闭包中使用循环变量是一个常见的坑。务必注意。

    go 复制代码
    // Go 1.21及之前:有问题
    for _, v := range []int{1, 2, 3} {
        go func() {
            fmt.Println(v) // 可能全部打印3
        }()
    }
    // 修正:将变量作为参数传入
    for _, v := range []int{1, 2, 3} {
        go func(val int) {
            fmt.Println(val) // 正确打印1,2,3
        }(v)
    }

    (Go 1.22及之后版本修复了此问题,循环变量每次迭代会创建新实例)

7. 总结

Go语言通过单一的for关键字,配合灵活的语法和强大的range关键字,提供了清晰、高效且安全的循环机制。记住核心几点:

  • 只有for,没有whiledo-while ,但for可以模拟它们。
  • range是遍历容器和通道的首选,它安全、简洁,并能正确处理Unicode。
  • 善用breakcontinue和带标签的控制流来处理复杂的循环逻辑。
  • 注意循环变量的作用域和闭包捕获问题,尤其是在旧版本Go中。

掌握这些循环语句,你就能优雅地处理Go程序中的重复性任务。

相关推荐
謓泽1 小时前
C语言不是语法,是通往机器的地图。
c语言·开发语言
云水一下1 小时前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
飞天狗1111 小时前
零基础JavaWeb入门——第五课第二小节:九大内置对象 · 第2个:response(响应对象)
java·开发语言
DJ斯特拉1 小时前
axios快速使用
开发语言·前端·javascript
xingpanvip1 小时前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
于先生吖2 小时前
教育类Java实战项目:在线错题整理平台分层架构设计与接口源码解析
java·开发语言
桥田智能2 小时前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
想吃火锅10052 小时前
【leetcode】88.合并两个有序数组js
算法
开发小能手-roy2 小时前
StringBuilder vs StringBuffer:2024年还需要线程安全字符串吗?
开发语言·python·安全