/ Go 语言 range 关键字完全指南 /
range 是 Go 语言中非常重要的关键字,用来在各种数据结构上进行迭代,比如数组、切片、Map 等。其中,在切片上使用 range 进行循环迭代是非常常见的。
本文将全面介绍在切片上使用 range 的相关知识,包括:
- range 关键字的作用
- range 基本语法
- 循环切片的索引和值
- range 返回值数量
- 只遍历索引或值
- range 的原理分析
- range 的常见用法
- range 迭代 map
- range 的性能优化
- range 的一些坑
通过详细的示例,可以全面了解在 Go 语言切片上使用 range 的诸多技巧,掌握 range 关键字的精髓所在。相信本文将是你学习 range 的最佳指南。
1
1. range 关键字
range 关键字用于基于数组或切片等进行迭代,它可以返回每个迭代的索引和值。基本语法如下:
for idx, val := range coll { }
range 返回集合的索引和值,通过这种方式可以进行各种迭代操作。
2
2. range 基本语法
对一个切片使用 range 进行迭代访问其元素:
func main() { nums := []int{10, 20, 30} for i, v := range nums { fmt.Println(i, v) } }
输出
0 10 1 20 2 30
range 默认返回两个值,索引和值。索引从 0 开始。
如果不需要索引,可以使用 _ 空标识符:
for _, v := range nums { fmt.Println(v) }
3. 循环切片索引和值
下面是切片 range 返回的索引和值的示例:
func main() { fruits := []string{"apple", "banana", "orange"} for i, v := range fruits { fmt.Printf("index: %d, value: %s\n", i, v) } }
输出:
index: 0, value: apple index: 1, value: banana index: 2, value: orange
可以看出,range 可以同时返回切片元素的索引和值。
一般 i 表示索引,v 表示值。根据需要决定是否忽略。
4
4. range 返回值数量
range 有几种不同的返回值数量:
for idx := range slice // 只返回索引 for _, value := range slice // 只返回值 for idx, value := range slice // 返回索引和值
可以根据需要选择返回一个或两个值。
返回两个值可以同时获得索引和值信息。返回一个值可以减少不必要的变量。
5
5. 只遍历索引或值
如果只需要索引,可以:
for i := range slice { // 使用索引i }
只需要值,使用空标识符 _:
for _, v := range slice { // 使用值v }
这在不同场景下都很常用。
6
6. range 的原理
range 在底层是基于指针进行的迭代:
for ptr, end := &slice[0], &slice[len(slice)]; ptr != end; ptr++ { // ptr是指向每个元素的指针 }
range 做了这样的工作:
- 获取开始和结束指针
- 指针自增访问每个元素
- 返回索引和值
理解这一点有助于分析 range 的行为。
7
7. range 常见用法
range 的常见用法有下面这些:
- 数组或切片遍历
- Map 遍历
- 字符串遍历
- Channel 遍历
7.1
7.1 数组遍历
func main() { nums := [5]int{1, 2, 3, 4, 5} for i, v := range nums { fmt.Println(i, v) } }
数组也可以直接使用 range 进行遍历。
7.2
7.2 Map 遍历
遍历 map 时,range 返回 key 和 value:
func main() { kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Println(k, v) } }
遍历 map 时,每次遍历的顺序是不固定的。
7.3
7.3 字符串遍历
字符串在 Go 里是字节切片,也可以用 range 迭代:
遍历 map 时,每次遍历的顺序是不固定的。 7.3 7.3 字符串遍历 字符串在 Go 里是字节切片,也可以用 range 迭代:
需要转换成 string 打印,因为直接打印字节值。
7.4
7.4 Channel 遍历
对一个 channel 使用 range 可以不断获取发送的值:
func main() { ch := make(chan int) go func() { ch <- 1 ch <- 2 close(ch) }() for v := range ch { fmt.Println(v) } }
需要关闭 channel 来结束 range。
这些是 range 最典型的用法。
8
8. range 迭代 map
对 map 使用 range 有一些特殊行为需要注意。
- 随机顺序:每次遍历顺序可能不一样
- 仅值:range map 只返回值
- 删除和并发:删除或并发读写可能导致 panic
比如:
func main() { m := map[string]int{"a": 1, "b": 2} for k, v := range m { println(k, v) } }
多次运行会发现每次 k 的顺序都可能不同。
如果需要固定顺序,需要先将 key 放到切片里排序。
range 遍历 map 时如果同时在另一个 goroutine 删除 key,会导致 panic 异常。
9
9. range 优化
range 在迭代切片和数组时会生成大量临时变量,可以通过以下方法优化:
- 复用变量
- 减少不必要变量
- 提前计算长度
- 首尾两段分别遍历
- 使用指针直接遍历
例如:
var length = len(slice) for i := 0; i < length; i++ { value := slice[i] // 迭代元素 }
这样可以减少变量生成,同时也能提高一些效率。
10
10. range 的坑
range 虽然常用,但是容易踩到一些坑,主要有下面这些:
- 范围变量重用问题
- 循环变量逃逸导致错误
- range channel 必须关闭
- range map 随机顺序
比如使用循环变量时:
funcs := []func(){} for _, f := range funcs { go f() // 使用的是同一个f }
要避免重用变量,最安全的方式是在循环里重新定义变量:
要避免重用变量,最安全的方式是在循环里重新定义变量:
11
总结
通过本文,我们全面介绍了 Go 语言中使用 range 迭代切片的相关内容。range 是 Go 语言中非常重要的一个设计,可以巧妙地在各种数据结构上进行遍历。理解 range 的设计思想和语法用法可以让我们更好地编写 Go 代码。如果你在使用 range 时还有其他疑问,欢迎留言讨论。