Go | range 用法总结与风险规避

在 Go 语言中,range 是用于遍历集合的关键字,用法简洁灵活。本文将对其用法做总结。

基本语法

range 遍历集合时,会返回 索引(或键) 和 对应的值,语法格式如下:


for 索引/(键, 值) := range 集合 {
    // TODO
}

● 若只需要值,可使用匿名变量 _ 忽略索引/键;

● 若只需要索引/键,可省略值的变量。

遍历不同类型的集合

array/slice

● 返回值:索引 和 元素值(值拷贝,修改遍历变量不影响原数组/切片)。

● 示例


nums := []int{1, 2, 3}
for i, v := range nums {
    fmt.Printf("索引: %d, 值: %d\n", i, v)
}
// 输出:
// 索引: 0, 值: 1
// 索引: 1, 值: 2
// 索引: 2, 值: 3

string

● 返回值:字节索引 和 Unicode 码点(rune)(字符串按 Unicode 字符遍历,非 ASCII 字符可能占多个字节)。

● 示例


s := "hello 世界"
for i, c := range s {
    fmt.Printf("索引: %d, 字符: %c\n", i, c)
}
// 输出(中文占三个字节)
索引: 0, 字符: h
索引: 1, 字符: e
索引: 2, 字符: l
索引: 3, 字符: l
索引: 4, 字符: o
索引: 5, 字符:
索引: 6, 字符: 世
索引: 9, 字符: 界

map

● 返回值:键(key) 和 值(value)(遍历顺序不固定,每次可能不同)。

● 示例


m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
    fmt.Printf("键: %s, 值: %d\n", k, v)
}
// 可能输出:
// 键: a, 值: 1
// 键: b, 值: 2

channel

● 返回值:通道中接收的数据(无索引/键,若通道关闭则退出循环)。

● 示例


ch := make(chan int, 2)

ch <- 1
ch <- 2

close(ch)

for v := range ch { // 只返回值
    fmt.Println(v)
}

// 输出:
// 1
// 2

用于控制循环次数


// 循环固定次数,返回当前的循环次数
for i := range 5 {
    fmt.Println("i = ", i)
}

// 固定循环次数,忽略当前循环次数
for range 5 {
    // TODO:
}

注意事项及规避方法

遍历变量的复用

range 遍历中,索引和值的变量是复用的(即每次循环使用同一个内存地址)。若在循环中保存变量地址,可能导致意外结果。


nums := []int{1, 2, 3}
var ps []*int

for _, v := range nums {
    // 错误:所有元素都指向同一个v的地址
    ps = append(ps, &v) 
}

// ps 中的指针可能全指向 3(最后一次循环的值)

解决方法:使用临时变量或直接取原数组地址。


for i := range nums {
    // 正确:取原元素地址
    ps = append(ps, &nums[i]) 
}

数组遍历与长度

遍历数组时,range 会按数组的实际长度遍历,而非指针或切片的长度。


arr := [3]int{1, 2, 3}
slice := arr[:2] // 切片长度为2

for i, v := range arr { // 遍历数组,3次循环
    fmt.Println(i, v)
}

for i, v := range slice { // 遍历切片,2次循环
    fmt.Println(i, v)
}

map遍历的删除与新增

● 遍历中删除已遍历的键:安全,不影响后续遍历。

● 遍历中新增键:新增的键可能被遍历到,也可能不被遍历到(不确定)。

字符串遍历与字节索引

非 ASCII 字符(如中文)在字符串中占多个字节,range 返回的索引是字节索引,而非字符位置。

s := "世界"
// 输出 6(每个中文字符占3字节)
fmt.Println(len(s)) 
for i, c := range s {

    // 索引为 0 和 3
    fmt.Printf("索引: %d, 字符: %c\n", i, c) 
}
此条目发表在Go分类目录。将固定链接加入收藏夹。