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 oldMap或for 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