Golang 中你应该知道的 Range 知识
1.1 for range 概述及使用
for range
是 Go 语言中非常常用的循环结构,用于遍历多种数据结构,例如数组、slice、map、字符串以及 channel。通过 for range
,可以简洁地获取集合中的索引和值。
基本语法:
for index, value := range collection {}
- collection: 要遍历的数据结构,如数组、切片、映射、字符串或通道。
- index: 当前元素的索引,对于映射,索引为键 (key)。
- value: 当前索引位置的值。
如果不需要索引,可以使用下划线 (_
) 忽略:
for _, value := range collection {}
1.2 for range 使用场景
1.2.1 遍历数组
数组是固定大小的序列,for range
可以用于遍历数组的每个元素。
arr := [5]int{10, 20, 30, 40, 50}
for i, v := range arr {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}
1.2.2 遍历切片
切片是数组的引用,长度可以动态变化。for range
可以用于切片的遍历。
slice := []int{100, 200, 300, 400, 500}
for i, v := range slice {
fmt.Printf("Index: %d, Value: %d\n", i, v)
}
1.2.3 遍历 map
for range
可以用于遍历 map
中的键值对。
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
注意:
map
的遍历顺序是随机的,每次执行可能不同。
1.2.4 遍历字符串
for range
可以逐个字符遍历字符串,并支持 Unicode 字符。
str := "Hello, 世界"
for i, r := range str {
fmt.Printf("Index: %d, Rune: %c\n", i, r)
}
注意:
for range
遍历字符串时,处理的是 Unicode 字符(rune
),而不是字节。
1.2.5 遍历 channel
for range
可以用于从通道接收数据,直到通道关闭。
ch := make(chan int, 5)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
注意:
- 通道必须关闭,否则
for range
会一直阻塞。for range
会在通道关闭后自动退出循环。
1.3 for range 使用中的常见错误
错误示例 1:指针问题
type user struct {
name string
age uint64
}
func main() {
u := []user{
{"wesley", 20},
{"wei", 30},
{"wesleywei", 40},
}
n := make([]*user, 0, len(u))
for _, v := range u {
n = append(n, &v)
}
fmt.Println(n)
for _, v := range n {
fmt.Println(v)
}
}
在 Go 1.22 之前,输出会是:
[0xc00000c048 0xc00000c048 0xc00000c048]
&{wesleywei 40}
&{wesleywei 40}
&{wesleywei 40}
原因是每次循环都引用了同一个变量 v
的地址。
错误示例 2:闭包问题
func main() {
var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() {
fmt.Println(i)
})
}
for _, f := range funcs {
f()
}
}
在 Go 1.22 之前,输出为:
3
3
3
这是因为闭包捕获的是循环变量 i
的引用,而不是每次的值。
从 Go 1.22 开始,Go 修复了此问题,输出结果为:
0
1
2
1.4 range 的特殊用法
安全删除 map 中的元素
在 Go 中,可以安全地在 for range
循环中删除 map
中的元素。
func main() {
d := map[string]string{
"wesley": "wei",
"medium": "blog",
}
for k := range d {
if k == "wesley" {
delete(d, k)
}
}
fmt.Println(d)
}
输出:
map[medium:blog]
这种操作在其他语言中(如 Python 或 Java)可能会引发错误或异常。
动态添加 map 中的元素
可以在 for range
遍历 map
时动态添加元素:
func main() {
t := map[string]string{
"wesley": "handsome",
"wesley1": "handsome1",
"wesley2": "handsome2",
}
for k := range t {
t["wesley3"] = "handsome3"
fmt.Printf("%s-%s ", k, t[k])
}
}
每次执行的结果可能不同,原因是 map
的哈希表特性导致遍历顺序随机。
1.5 总结
for range
是 Go 语言中非常强大的循环结构,可以遍历多种类型的数据结构。通过掌握其使用场景、常见错误和特殊用法,你可以更高效地处理复杂的数据操作。