首页 / Go 语言入门教程 / range 与新遍历方式

Go 语言入门教程

range 与新遍历方式

本教程共 80 篇 · 第 20 篇 · 更新于 2026-07-27 · 约 8 分钟阅读

GoGo 入门教程rangerange over intrange over func迭代器Go 1.22Go 1.23

20. range 与新遍历方式

本节目标:掌握 Go 1.22 引入的 range over int 和 Go 1.23 引入的 range over func,了解 Go 循环遍历的最新写法。

回顾:range 的传统用法

在 Go 1.22 之前,for-range 只能遍历这些类型:

  • 数组 [N]T
  • 切片 []T
  • 字符串 string
  • map map[K]V
  • channel chan T
// 遍历切片
for i, v := range []int{10, 20, 30} {
    fmt.Println(i, v)
}

// 遍历字符串
for i, r := range "Hello" {
    fmt.Println(i, r)
}

range over int(Go 1.22)

Go 1.22 新增了一个简洁的写法:直接 range 一个整数。

基本用法

for i := range 10 {
    fmt.Println(i)
}
// 输出:0 1 2 3 4 5 6 7 8 9

range 10 表示从 0 遍历到 9,共 10 次。等价于:

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

只需要循环次数

如果你不需要索引值:

for range 5 {
    fmt.Println("执行一次")
}
// 输出5次"执行一次"

为什么需要这个

以前要循环 N 次,你得写 for i := 0; i < N; i++,比较啰嗦。现在 for i := range N 就行,更简洁、更不容易出错。

实际例子:

// 生成5个随机数
var nums []int
for range 5 {
    nums = append(nums, rand.Intn(100))
}
fmt.Println(nums)

// 倒计时
for i := range 10 {
    fmt.Println(10 - i)
}
fmt.Println("发射!")
Note

range N 中 N 必须是非负整数。如果 N 是 0 或负数,循环体不会执行。

range over func(Go 1.23)

Go 1.23 引入了一个更强大的特性:for-range 可以遍历迭代器函数

什么是迭代器函数

迭代器函数是特定签名的函数,用来产生一系列值。Go 1.23 支持三种签名:

func(yield func() bool)           // 不产出值,只控制循环
func(yield func(K) bool)          // 产出一个值
func(yield func(K, V) bool)       // 产出两个值

yield 是一个回调函数,迭代器调用它来产出每次循环的值。如果 yield 返回 false,表示停止迭代。

基本示例

来看一个简单的迭代器:

// 定义一个迭代器函数:产出 1 到 n
func CountUp(n int) func(yield func(int) bool) {
    return func(yield func(int) bool) {
        for i := 1; i <= n; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

func main() {
    for v := range CountUp(5) {
        fmt.Println(v)
    }
    // 输出:1 2 3 4 5
}

工作流程:

  1. for-range 调用 CountUp(5),得到迭代器函数
  2. 迭代器函数内部循环 1 到 5,每次调用 yield(i) 产出值
  3. for-range 的循环体接收到值 v,执行循环体
  4. 循环体执行完后,yield 返回 true,迭代器继续
  5. 如果 yield 返回 false(比如 break 了),迭代器提前退出

产出两个值

// 产出索引和值的迭代器
func Enumerate(items []string) func(yield func(int, string) bool) {
    return func(yield func(int, string) bool) {
        for i, v := range items {
            if !yield(i, v) {
                return
            }
        }
    }
}

func main() {
    names := []string{"Go", "Python", "Rust"}
    for i, name := range Enumerate(names) {
        fmt.Printf("%d: %s\n", i, name)
    }
}

提前终止

for-range 中使用 break,迭代器会收到 yield 返回 false,然后提前退出:

for v := range CountUp(100) {
    fmt.Println(v)
    if v >= 4 {
        break  // 产出 4 后 yield 返回 false,迭代器停止
    }
}
// 输出:1 2 3 4(在产出5之前break了)

标准库 iter 包

Go 1.23 同时新增了 iter 标准库包,定义了两个类型别名:

type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)

iter.Seqiter.Seq2 可以让你的迭代器函数签名更简洁:

import "iter"

// 用 iter.Seq 替代手写签名
func CountUp(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 1; i <= n; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

实际用途

迭代器函数的最大好处是自定义遍历逻辑,不需要把数据全部放到切片里:

// 按行读取文件的迭代器
func Lines(r io.Reader) iter.Seq[string] {
    return func(yield func(string) bool) {
        scanner := bufio.NewScanner(r)
        for scanner.Scan() {
            if !yield(scanner.Text()) {
                return
            }
        }
    }
}

// 使用
file, _ := os.Open("data.txt")
defer file.Close()

for line := range Lines(file) {
    fmt.Println(line)
    if strings.Contains(line, "STOP") {
        break  // 遇到 STOP 就停止读取
    }
}

以前要实现这种「遇到条件就停止」的遍历,需要写很多样板代码。现在一个迭代器函数就够了。

Tip

slices 包在 Go 1.23 新增了几个迭代器函数,比如 slices.All 返回索引和值的迭代器,slices.Values 只返回值的迭代器:

import "slices"

nums := []int{10, 20, 30}

// slices.All 产出索引和值
for i, v := range slices.All(nums) {
    fmt.Println(i, v)
}

// slices.Values 只产出值
for v := range slices.Values(nums) {
    fmt.Println(v)
}

// slices.Backward 反向遍历
for i, v := range slices.Backward(nums) {
    fmt.Println(i, v)
}

新旧对比

遍历 N 次

// Go 1.22 之前
for i := 0; i < n; i++ {
    // ...
}

// Go 1.22+
for i := range n {
    // ...
}

自定义遍历

// Go 1.23 之前:需要返回切片或用回调
func GetLines() []string {
    // 把所有数据收集到切片里返回
}

// Go 1.23+:用迭代器,按需产出
func Lines(r io.Reader) iter.Seq[string] {
    // 按需产出,不占额外内存
}

版本要求

特性最低版本说明
range over intGo 1.22for i := range 10
range over funcGo 1.23for v := range iterFunc
iter 包Go 1.23iter.Seq / iter.Seq2
slices.All/Values/BackwardGo 1.23标准库内置迭代器
Warning

使用这些新特性需要确保 go.mod 中的 go 版本至少是 1.23。如果版本不够,编译会报错。

常见问题

range over int 从 0 开始

for i := range 5 {
    fmt.Println(i)  // 0 1 2 3 4
}

总是从 0 开始,到 N-1 结束。不能指定起始值。如果需要从其他值开始,做个偏移:

for i := range 5 {
    fmt.Println(i + 10)  // 10 11 12 13 14
}

迭代器中 yield 返回 false 后不要继续

func BadIter() iter.Seq[int] {
    return func(yield func(int) bool) {
        yield(1)
        yield(2)  // 如果 yield(1) 返回 false,这里不应该继续调用
        yield(3)
    }
}

正确做法是检查返回值:

return func(yield func(int) bool) {
    if !yield(1) { return }
    if !yield(2) { return }
    if !yield(3) { return }
}

学完这节你能做什么

  • for i := range N 简洁地循环 N 次
  • 理解迭代器函数的概念和三种签名
  • 写自定义迭代器函数并用 for-range 遍历
  • 用标准库 slices.All / slices.Values 等迭代器
  • 知道这两个特性的版本要求

下一节讲循环变量语义变更和跳转语句。