You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.6 KiB

title
for-range语法

遍历 - for-range语法

for-range语法可以用来遍历数组、指向数组的指针切片、字符串、映射和通道。

遍历数组

当遍历一个数组a时候循环范围会从0到len(a) -1

func main() {
	var a [3]int
	for i, v := range a {
		fmt.Println(i, v)
	}

    for i, v := range &a {
		fmt.Println(i, v)
	}
}

遍历切片

当遍历一个切片s时候循环范围会从0到len(s) -1若切片是nil则迭代次数是0次

func main() {
	a := make([]int, 3)
	for i, v := range a {
		fmt.Println(i, v)
	}

    a = nil
    for i, v := range a {
		fmt.Println(i, v)
	}
}

for-range切片时候可以边遍历边append吗

当遍历切片时候可以边遍历边append操作这并不会造成死循环。因为遍历之前已经确定了循环范围遍历操作相当如下伪代码

len_temp := len(range) // 循环上界
range_temp := range
for index_temp = 0; index_temp < len_temp; index_temp++ {
    value_temp = range_temp[index_temp]
    index = index_temp
    value = value_temp
    original body
}

for-range切片时候返回的是值拷贝

无论遍历数组还是切片,返回都是数组或切片中的值拷贝:

func main() {
	users := []User{
		{
			Name: "a1",
			Age:  100,
		},
		{
			Name: "a2",
			Age:  101,
		},
		{
			Name: "a2",
			Age:  102,
		},
	}

	fmt.Println("before: ", users)
	for _, v := range users {
		v.Age = v.Age + 10 // 想给users中所有用户年龄增加10岁
	}
	fmt.Println("after:  ", users)

}

执行上面代码,输入以下内容:

before:  [{a1 100} {a2 101} {a2 102}]
after:   [{a1 100} {a2 101} {a2 102}]

解决办法可以通过索引访问原切片或数组:

func main() {
	users := []User{
		{
			Name: "a1",
			Age:  100,
		},
		{
			Name: "a2",
			Age:  101,
		},
		{
			Name: "a2",
			Age:  102,
		},
	}

	fmt.Println("before: ", users)
	for i := range users {
		users[i].Age = users[i].Age + 10
	}
	fmt.Println("after:  ", users)
}

遍历字符串

当遍历字符串时候返回的是rune类型rune类型是int32类型的别名一个rune就是一个码点(code point)

// rune is an alias for int32 and is equivalent to int32 in all ways. It is
// used, by convention, to distinguish character values from integer values.
type rune = int32

由于遍历字符串时候返回的是码点所以索引并不总是依次增加1的

func main() {
	var str = "hello你好"
	var buf [100]byte
	for i, v := range str {
		vl := utf8.RuneLen(v)
		si := i + vl
		copy(buf[:], str[i:si])
		fmt.Printf("索引%2d: %q\t 码点: %#6x\t 码点转换成字节: %#v\n", i, v, v, buf[:vl])
	}
}

执行上面代码将输出以下内容:

索引 0: 'h'	 码点:   0x68	 码点转换成字节: []byte{0x68}
索引 1: 'e'	 码点:   0x65	 码点转换成字节: []byte{0x65}
索引 2: 'l'	 码点:   0x6c	 码点转换成字节: []byte{0x6c}
索引 3: 'l'	 码点:   0x6c	 码点转换成字节: []byte{0x6c}
索引 4: 'o'	 码点:   0x6f	 码点转换成字节: []byte{0x6f}
索引 5: ''	 码点: 0xff0c	 码点转换成字节: []byte{0xef, 0xbc, 0x8c}
索引 8: '你'	 码点: 0x4f60	 码点转换成字节: []byte{0xe4, 0xbd, 0xa0}
索引11: '好'	 码点: 0x597d	 码点转换成字节: []byte{0xe5, 0xa5, 0xbd}

遍历映射

当遍历映射时候Go语言是不会保证遍历顺序的为了明确强调这一点Go语言在实现的时候故意随机地选择一个桶开始遍历。当映射通道为nil时候遍历次数为0次。

func main() {
	m := map[int]int{
		1: 10,
		2: 20,
		3: 30,
	}

	for i, v := range m {
		fmt.Println(i, v)
	}

	m = nil
	for i, v := range m {
		fmt.Println(i, v)
	}
}

for-range映射时候可以边遍历边新增或删除吗

若在一个Goroutine里面边遍历边新增、删除理论上是可以的不会触发写检测的新增的key-value可能会被访问到也可能不会。

若多个Goroutine中进行遍历、新增、删除操作的话是不可以的是可能触发写检测的然后直接panic。

遍历通道

当遍历通道时直到通道关闭才会终止若通道是nil则会永远阻塞。遍历通道源码分析请见《[运行时篇-通道-从channel中读取数据]({{< relref "concurrency/channel#从channel中读取数据" >}}) 》。

进一步阅读