初面网初面网

Range 范围

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。

  • 数组、切片、字符串返回索引和值
  • map 返回 key-value 对
  • 通道(channel)只返回通道内的值

格式:

for key, value := range oldMap {
    newMap[key] = value
}

key 和 value 是可以省略的:

  • 只想读取 key:for key := range oldMapfor key, _ := range oldMap
  • 只想读取 value:for _, value := range oldMap

数组和切片

package main

import "fmt"

// 声明一个包含 2 的幂次方的切片
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    // 遍历 pow 切片,i 是索引,v 是值
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

字符串

range 迭代字符串时,返回每个字符的字节索引和 Unicode 代码点(rune)。

package main

import "fmt"

func main() {
    for i, c := range "hello" {
        fmt.Printf("index: %d, char: %c\n", i, c)
    }
}

注意:遍历字符串时 range 返回的是字节下标和 Unicode 码点(rune),而不是字符下标。含中文时,中文每个字占3个字节,所以下标会跳着走。Go 中的中文采用 UTF-8 编码,逐个遍历字符必须采用 for-each(range)形式

package main

import "fmt"

func main() {
    // "中"占3个字节,所以下标是0、3、6
    for i, c := range "中国人" {
        fmt.Printf("index: %d, char: %c\n", i, c)
    }
}

映射 Map

package main

import "fmt"

func main() {
    // 创建一个空的 map,key 是 int 类型,value 是 float32 类型
    map1 := make(map[int]float32)

    map1[1] = 1.0
    map1[2] = 2.0
    map1[3] = 3.0
    map1[4] = 4.0

    // 遍历 key 和 value
    for key, value := range map1 {
        fmt.Printf("key is: %d - value is: %f\n", key, value)
    }

    // 只读取 key
    for key := range map1 {
        fmt.Printf("key is: %d\n", key)
    }

    // 只读取 value
    for _, value := range map1 {
        fmt.Printf("value is: %f\n", value)
    }
}

map 是无序的,遍历输出顺序不固定

通道 Channel

range 遍历从通道接收的值,直到通道关闭。

package main

import "fmt"

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    close(ch)

    // 通道只返回值,不返回索引
    for v := range ch {
        fmt.Println(v)
    }
}

忽略值

在遍历时可以使用 _ 来忽略索引或值。

package main

import "fmt"

func main() {
    nums := []int{2, 3, 4}

    // 忽略索引
    for _, num := range nums {
        fmt.Println("value:", num)
    }

    // 忽略值
    for i := range nums {
        fmt.Println("index:", i)
    }
}

综合示例

package main

import "fmt"

func main() {
    // 使用 range 求一个 slice 的和
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)

    // 在数组上使用 range 传入索引和值两个变量
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }

    // range 用在 map 的键值对上
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }

    // 枚举 Unicode 字符串,第一个参数是索引,第二个是 Unicode 的值
    for i, c := range "go" {
        fmt.Println(i, c)
    }
}

range 的坑:迭代变量复用

Go 1.22 之前,range 的迭代变量(k、v)在整个循环中是复用的同一个变量,每次迭代都是赋值覆盖。如果在循环中把变量的地址(如 &v)存起来或用在闭包里,最后取到的都是最后一次迭代的值。Go 1.22 起,每次迭代都会创建新的变量,此问题被修复

package main

import "fmt"

func main() {
    nums := [3]int{5, 6, 7}
    for k, v := range nums {
        // v 是同一个地址,每次迭代都是覆盖赋值,并不是每次新建变量(Go 1.22 前)
        fmt.Println("源值地址:", &nums[k], " value的地址:", &v)
    }
}

输出(Go 1.22 前 value 的地址全部相同):

源值地址: 0xc000012138  value的地址: 0xc00000e0a8
源值地址: 0xc000012140  value的地址: 0xc00000e0a8
源值地址: 0xc000012148  value的地址: 0xc00000e0a8

所以在 Go 1.22 之前,不要在循环内直接取 range 变量的地址或放入闭包,如果需要可以复制一份:v := v

更新于 2026/8/13