乐趣区

关于go:Go语言中常见错误

介绍

下列的一些 Go 语言常见谬误可能对老手学习 Go 语言有很大帮忙

应用援用循环迭代器变量

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

它将会输入

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

解释: 在每次迭代中,咱们将 i 的地址附加到 out 切片中,但因为它是雷同的变量,所以咱们将附加雷同的地址,最终蕴含调配给 i 的最初一个值。解决方案之一是将循环变量复制到一个新变量中:

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

此时的输入是合乎预期的
解释: 行 i:= i 将循环变量 i 复制到一个新的变量作用域,该变量作用域位于 for 循环体块中,也称为 i。新变量的地址是被附加到数组中的地址,这使得它比 for 循环体块的寿命更长。在每个循环迭代中创立一个新变量。

尽管这个例子可能看起来有点显著,但同样的意外行为可能在其余一些状况下暗藏得更多。例如,循环变量能够是一个数组,援用能够是一个切片:

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

输入:

Values: [[3] [3] [3]]

当循环变量在 Goroutine 中应用时,也会呈现同样的问题(参见下一节)。

在循环迭代器变量上应用 goroutines

在 Go 中迭代时,可能会尝试应用 goroutine 并行处理数据。例如,你能够这样写,应用闭包:

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

下面的 for 循环可能不会达到您的预期成果,因为它们的 val 变量实际上是一个独自的变量,它承受每个 slice 元素的值。因为闭包都只绑定到那个变量,所以在运行这段代码时,很有可能看到每次迭代都打印最初一个元素,而不是按程序打印每个值,因为 goroutine 很可能直到循环完结后才开始执行。

写这个闭包的正确办法是:

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

通过将 val 作为参数增加到闭包中,val 将在每次迭代时被求值并放在 goroutine 的堆栈上,因而在最终执行 goroutine 时,每个 slice 元素都是可用的。

同样重要的是要留神,在循环体中申明的变量在迭代之间是不共享的,因而能够在闭包中独自应用。上面的代码应用一个公共索引变量 i 来创立独自的 val,这将导致预期的行为:

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

留神,如果不将此闭包作为 go 协程执行,代码将按预期运行。上面的示例输入 1 到 10 之间的整数。

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

只管闭包依然对雷同的变量敞开 (在本例中为 i),但它们在变量更改之前执行,从而产生所需的行为。
上面的例子也是相似的

for _, val := range values {go val.MyMethod()
}

func (v *val) MyMethod() {fmt.Println(v)
}

下面的例子也将打印值的最初一个元素,起因与闭包雷同。要修复该问题,在循环中申明另一个变量。

for _, val := range values {
    newVal := val
    go newVal.MyMethod()}

func (v *val) MyMethod() {fmt.Println(v)
}
退出移动版