在 Go 语言开发中,for range 是最常用的遍历语法之一。很多开发者可能只知道它可以用来遍历切片(slice)和映射(map),但实际上 for range 的能力远不止于此。

常见用法

1. 遍历切片(Slice)

遍历切片是 for range 最常见的用法之一,它会返回索引和元素值:

slice := []string{"Go", "Python", "Java"}
for index, value := range slice {
    fmt.Printf("索引: %d, 值: %s\n", index, value)
}
// 输出:
// 索引: 0, 值: Go
// 索引: 1, 值: Python
// 索引: 2, 值: Java

2. 遍历映射(Map)

遍历映射同样常用,它会返回键和值:

m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
    fmt.Printf("键: %s, 值: %d\n", key, value)
}
// 可能的输出(顺序随机):
// 键: a, 值: 1
// 键: b, 值: 2
// 键: c, 值: 3

注意:map 的遍历顺序是随机的,每次运行可能不同。

你可能不知道的用法

除了切片和映射,for range 还支持遍历以下三种类型,这些用法在实际开发中同样非常有用。

1. 遍历数组(Array)

遍历数组的语法与切片相同,返回索引和元素值:

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

注意:遍历数组时,返回的元素值是数组元素的副本,修改该值不会影响原数组。

2. 遍历字符串(String)

这是一个非常实用但容易被忽略的特性。遍历字符串时,for range 会返回字符的索引和对应的 Unicode 码点(rune):

str := "Hello 世界"
for index, char := range str {
    fmt.Printf("索引: %d, 字符: %c\n", index, char)
}
// 输出:
// 索引: 0, 字符: H
// 索引: 1, 字符: e
// 索引: 2, 字符: l
// 索引: 3, 字符: l
// 索引: 4, 字符: o
// 索引: 5, 字符:  
// 索引: 6, 字符: 世
// 索引: 9, 字符: 界

重要特性

  • 字符串遍历会自动处理 Unicode 字符,每个中文字符占 3 个字节,但会被视为一个完整的 rune。
  • 索引值是字符的起始字节位置,不是字符的序号。

这个特性在处理包含中文、emoji 等多字节字符的字符串时特别有用,避免了手动处理 UTF-8 编码的麻烦。

3. 遍历通道(Channel)

这是一个非常优雅的特性。遍历通道时,for range 会持续从通道接收值,直到通道被关闭:

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)

for value := range ch {
    fmt.Println("从通道接收:", value)
}
// 输出:
// 从通道接收: 1
// 从通道接收: 2
// 从通道接收: 3

重要特性

  • 如果通道未关闭,for range 会一直阻塞等待新值。
  • 遍历通道时,只返回通道中的值,没有索引。
  • 当通道关闭后,for range 会自动退出循环。

这种写法比传统的 for-select 模式更加简洁,是处理通道数据的推荐方式。

for range 的特殊用法

1. 只需要索引

可以使用下划线 _ 忽略值:

for i := range slice {
    fmt.Println("索引:", i)
}

2. 只需要值

可以省略索引变量:

for _, v := range slice {
    fmt.Println("值:", v)
}

3. 仅遍历次数

对于数组或切片,也可以直接遍历而不使用索引和值,用于执行固定次数的循环:

for range slice {
    fmt.Println("执行一次")
}

注意事项

遍历副本问题

当遍历数组、切片或字符串时,for range 返回的元素值是副本,修改该值不会影响原数据。如果需要修改原数据,应使用索引访问。

Map 遍历顺序问题

Map 的遍历顺序是随机的,不要依赖遍历顺序。如果需要固定顺序,应先将键排序,再按排序后的键访问。

通道阻塞问题

如果通道未关闭,for range 会一直阻塞等待新值。确保在发送完所有数据后关闭通道,避免程序卡死。

遍历过程中修改集合

在遍历 map 时,如果添加或删除元素,可能会影响遍历结果。虽然 Go 语言允许在遍历过程中修改 map,但行为是不确定的,建议避免这种做法。

写在最后

for range 是 Go 语言中一个功能强大的遍历工具,它不仅支持遍历切片和映射,还支持遍历数组、字符串和通道。每种类型都有其独特的特性:

类型 返回值 特性
数组 索引、元素值 元素是副本
切片 索引、元素值 元素是副本
字符串 索引、rune 自动处理 Unicode
映射 键、值 遍历顺序随机
通道 阻塞直到关闭

在实际开发中,建议根据具体需求选择合适的遍历方式。对于字符串处理,for range 的 Unicode 支持是一个很大的优势;对于通道遍历,for range 提供了最优雅的解决方案。