Golang 新手常犯错误之【循环迭代篇】

用户头像
卓丁
关注
发布于: 2020 年 07 月 25 日
Golang新手常犯错误之【循环迭代篇】

本文基于原文 Golang新手常犯的一些错误 ,并在其基础上进一步拓展与分析.

CommonMistakes[常见错误]

Table of Contents[目录]





Introduction[介绍]

When new programmers start using Go or when old Go programmers start using a new concept, there are some common mistakes that many of them make. Here is a non-exhaustive list of some frequent mistakes that show up on the mailing lists and in IRC.

当一些新手开始用Go语言或者Go老手开始使用新当概念时,许多人会犯一些常见的错误。
以下邮件列表和IRC中出现的一些常见错误的详尽列表。

Using reference to loop iterator variable[使用引用来循环迭代变量]

In Go, the loop iterator variable is a single variable that takes different values in each loop iteration. This is very efficient, but might lead to unintended behavior when used incorrectly. For example, see the following program:

在Go中,循环迭代器变量是单个变量,在每次循环迭代中采用不同的值。
这非常有效,但是如果使用不当,可能会导致意外行为。
如下举例:



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])
}



It will output unexpected results:



Values: 3 3 3
Addresses: 0x40e020 0x40e020 0x40e020




Explanation: in each iteration we append the address of i to the out slice, but since it is the same variable, we append the same address which eventually contains the last value that was assigned to i. One of the solutions is to copy the loop variable into a new variable:



for i := 0; i < 3; i++ {
+ i := i // Copy i into a new variable.
out = append(out, &i)
}



The new output of the program is what was expected:

Values: 0 1 2
Addresses: 0x40e020 0x40e024 0x40e028




Explanation: the line i := i copies the loop variable i into a new variable scoped to the for loop body block, also called i. The address of the new variable is the one that is appended to the array, which makes it outlive the for loop body block. In each loop iteration a new variable is created.



While this example might look a bit obvious, the same unexpected behavior could be more hidden in some other cases. For example, the loop variable can be an array and the reference can be a slice:



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]]




The same issue can be demonstrated also when the loop variable is being used in a Goroutine (see the following section).



Using goroutines on loop iterator variables[在循环迭代器变量上使用goroutines]



When iterating in Go, one might attempt to use goroutines to process data in parallel. For example, you might write something like this, using a closure:



for _, val := range values {
go func() {
fmt.Println(val)
}()
}



The above for loops might not do what you expect because their val variable is actually a single variable that takes on the value of each slice element. Because the closures are all only bound to that one variable, there is a very good chance that when you run this code you will see the last element printed for every iteration instead of each value in sequence, because the goroutines will probably not begin executing until after the loop.



The proper way to write that closure loop is:



for _, val := range values {
go func(val interface{}) {
fmt.Println(val)
}(val)
}



By adding val as a parameter to the closure, val is evaluated at each iteration and placed on the stack for the goroutine, so each slice element is available to the goroutine when it is eventually executed.



It is also important to note that variables declared within the body of a loop are not shared between iterations, and thus can be used separately in a closure. The following code uses a common index variable i to create separate vals, which results in the expected behavior:



for i := range valslice {
val := valslice[i]
go func() {
fmt.Println(val)
}()
}



Note that without executing this closure as a goroutine, the code runs as expected. The following example prints out the integers between 1 and 10.



for i := 1; i <= 10; i++ {
func() {
fmt.Println(i)
}()
}



Even though the closures all still close over the same variable (in this case, i), they are executed before the variable changes, resulting in the desired behavior. http://golang.org/doc/go_faq.html#closures_and_goroutines



You may find another, similar situation like the following:



for _, val := range values {
go val.MyMethod()
}
func (v *val) MyMethod() {
fmt.Println(v)
}



The above example also will print last element of values, the reason is same as closure. To fix the issue declare another variable inside the loop.



for _, val := range values {
newVal := val
go newVal.MyMethod()
}
func (v *val) MyMethod() {
fmt.Println(v)
}



拓展练习一(使用引用循环迭代)

请问以下代码有什么问题或者说会输出什么?

package main
type People struct {
Name string
Age int
}
func genPeople() {
m := make(map[string]*People)
ps := []People{
{Name: "A", Age: 1},
{Name: "B", Age: 2},
{Name: "C", Age: 3},
}
for _, p := range ps {
m[p.Name] = &p
}
for k, v := range m {
fmt.Println(k, v.Age)
}
}
func main() {
genPeople()
}



猛一看,没有问题呀?代码意图主要是遍历一个People数组,然后以People的Name作为key,封装生成一个map.

但是如果我们在genPeople()函数中加几行代码,将m 遍历打印出来的时候,结果就很令我们吃惊,

比如:

我们会发现输出结果中,m的三个元素的值竟然都是3,而并非我们想象的1,2,3,即:


zhou 22
li 22
wang 22




拓展练习二(待补充)



发布于: 2020 年 07 月 25 日 阅读数: 5
用户头像

卓丁

关注

鸟过无痕 2017.12.10 加入

泰戈尔:虽然天空没有留下我的痕迹,但我已飞过。

评论

发布
暂无评论
Golang新手常犯错误之【循环迭代篇】