在使用 Go 语言的for循环进行迭代时,有以下几点需要注意:

  1. Go 语言的 for 循环有三种形式,分别是标准的 for 循环、for-range 循环和 for-select 循环。你应该根据需要选择适当的循环形式。
  2. for 循环中的循环变量是局部变量,只会声明初始化一次,之后每次循环时重新赋值覆盖前面的。
  3. 在使用 for-select 循环时,你应该注意 select 关键字会不断尝试从通道中读取或者写入数据,直到有一个通道可以操作。如果所有的通道都不可操作,那么 select 关键字会执行 default 分支(如果有的话)。

示例1:使用循环迭代器的变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func main() {
	var out []*int
	for i := 0; i < 3; i++ {
		out = append(out, &i)
	}
	fmt.Println("Values:", *out[0], *out[1], *out[2])
	fmt.Println("Addresses:", out[0], out[1], out[2])
}
// Output
// Values: 3 3 3
// Addresses: 0x1400018c008 0x1400018c008 0x1400018c008

结果并不是预想中的1 2 3,而且他们的地址都是同一个,为什么会出现这种情况?这是由于for循环过程中,变量i只被创建了一次,变量i有自己的内存空间且地址在for循环过程中不变,循环过程中每次将i进行值传递。这样append到out里面的是i的地址,且每次都一样。循环到最后一个的时候,out里面的值就跟最后的一个相同了。

解决办法:使用临时变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
	var out []*int
	for i := 0; i < 3; i++ {
        i:=i
		out = append(out, &i)
	}
	fmt.Println("Values:", *out[0], *out[1], *out[2])
	fmt.Println("Addresses:", out[0], out[1], out[2])
}
// Output
// Values: 0 1 2
// Addresses: 0x14000122008 0x14000122010 0x14000122018

下面这个示例原因同上,

1
2
3
4
5
6
7
8
9
func main() {
	var out [][]int
	for _, i := range [][1]int{{1}, {2}, {3}} {
		out = append(out, i[:])
	}
	fmt.Println("Values:", out)
}
// Output
// Values: [[3] [3] [3]]

解决方法:使用临时变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

func main() {
	var out [][]int
	for _, i := range [][1]int{{1}, {2}, {3}} {
		i := i
		out = append(out, i[:])
	}
	fmt.Println("Values:", out)
}
// Output
// Values: [[1] [2] [3]]

示例2:在循环体内使用goroutines

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
	values := []int{1, 2, 3}
	wg := sync.WaitGroup{}
	for _, v := range values {
		wg.Add(1)
		go func() {
			fmt.Println(v)
			wg.Done()
		}()
	}
	wg.Wait()
}
// Output
// 3
// 3
// 3

主协程中的for循环很快就运行完成,此时子协程可能还没有运行,当开始运行的时候,v已经被最后一次循环赋值,所以这里打印的一直都是最后一个变量。

解决办法:使用闭包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
	values := []int{1, 2, 3}
	wg := sync.WaitGroup{}
	for _, v := range values {
		wg.Add(1)
		go func(i int) {
			fmt.Println(i)
			wg.Done()
		}(v)
	}
	wg.Wait()
}
// Output
// 3
// 1
// 2

参考