golang for循环的坑

golang for循环常见的问题、解决方案

遍历修改

这是初学者最容易犯的问题,当我们试图修改切片中的数据时,直接使用for range遍历数据并修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type Test struct {
	ID   int
	Name string
}

func TestForLoop() {

	var tests = []Test{
		{ID: 0, Name: "0"},
		{ID: 1, Name: "0"},
		{ID: 2, Name: "0"},
	}

	for i, test := range tests {
		test.Name = fmt.Sprintf("%d", i)
	}

	for _, test := range tests {
		fmt.Println(test)
	}
}

以上代码输出结果为:

1
2
3
{0 0}
{1 0}
{2 0}

原因

for i, test := range tests语句中的test是一个新定义的变量,对他的修改并不会影响到切片中原变量的值

解决方案

使用for i遍历切片:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type Test struct {
	ID   int
	Name string
}

func TestForLoop() {

	var tests = []Test{
		{ID: 0, Name: "0"},
		{ID: 1, Name: "0"},
		{ID: 2, Name: "0"},
	}

	for i := 0; i < len(tests); i++ {
		tests[i].Name = fmt.Sprintf("%d", i)
	}

	for _, test := range tests {
		fmt.Println(test)
	}
}

地址问题

先看代码,该代码定义了一个src切片和一个dist切片,遍历src切片并将遍历到的值地址添加到dist切片内,遍历dist切片并输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func TestForLoop() {

	src := []int{1, 2, 3, 4}
	var dist []*int

	for _, v := range src {
		dist = append(dist, &v)
	}
	for _, v := range dist {
		fmt.Println(*v)
	}
}

输出

1
2
3
4
5
/Users/mulinbiao/Library/Caches/JetBrains/IntelliJIdea2023.1/tmp/GoLand/___go_build_code
4
4
4
4

原因

在golang的for循环中,循环内部创建的函数变量都是共享同一块内存地址,for循环总是使用同一块内存去接收循环中的的value变量的值。不管循环多少次,value的内存地址都是相同的。

解决方法

定义临时变量tmp,将v的值赋给tmp,问题就解决了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

import "fmt"

func TestForLoop() {

	src := []int{1, 2, 3, 4}
	var dist []*int

	for _, v := range src {
		tmp:=v
		dist = append(dist, &tmp)
	}
	for _, v := range dist {
		fmt.Println(*v)
	}
}

变量快照

先看代码,该代码期望使用协程输出ints切片的值,但实际情况并不是。这个例子里,3个goroutine共享同一个变量i,最后输出的结果大概率是输出3 3 3。

要解决这个问题,主要有2个解决方案。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func TestForLoop() {
	ints := []int{1, 2, 3}
	for _, i := range ints {
		go func() {
			fmt.Printf("%v\n", i)
		}()
	}
}

方案一

把循环变量i作为goroutine函数的一个参数,编译器在执行go func(i int)时,就会解析到i的值,确保每个goroutine可以拿到自己想要的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func TestForLoop() {
	ints := []int{1, 2, 3}
	for _, i := range ints {
		go func(i int) {
			fmt.Printf("%v\n", i)
		}(i)
	}
}

方案二

创建一个新的变量,用于goroutine。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "fmt"

func TestForLoop() {
	ints := []int{1, 2, 3}
	for _, i := range ints {
		i := i
		go func() {
			fmt.Printf("%v\n", i)
		}()
	}
}
本博客已稳定运行 小时 分钟
共发表 31 篇文章 · 总计 82.93 k 字
本站总访问量
Built with Hugo
主题 StackJimmy 设计