range 与新遍历方式
本教程共 80 篇 · 第 20 篇 · 更新于 2026-07-27 · 约 8 分钟阅读
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
}
工作流程:
for-range调用CountUp(5),得到迭代器函数- 迭代器函数内部循环 1 到 5,每次调用
yield(i)产出值 for-range的循环体接收到值v,执行循环体- 循环体执行完后,
yield返回true,迭代器继续 - 如果
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.Seq 和 iter.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 int | Go 1.22 | for i := range 10 |
| range over func | Go 1.23 | for v := range iterFunc |
| iter 包 | Go 1.23 | iter.Seq / iter.Seq2 |
| slices.All/Values/Backward | Go 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等迭代器 - 知道这两个特性的版本要求
下一节讲循环变量语义变更和跳转语句。