go的十万个为什么?-函数和方法|Go主题月

282 阅读2分钟

本文中译于官方FAQ

原文地址:golang.org/doc/faq#Fun…

Go项目函数和方法

为什么 T 和 *T 具有不同的方法集?

Go规范所述,类型T的方法集由接收者类型T的所有方法组成,而对应的指针类型*T的方法集由接收者*TT的所有方法组成。这意味着*T的方法集包括T的方法集,但是T的方法集不包括*T的方法集。

之所以会出现这种区别,是因为如果接口值包含指针*T,则方法调用可以通过取消引用指针来获取值,但是如果接口值包含值T,则方法调用就没有安全的方法来获取指针(这样做将允许一种方法来修改接口内部值的内容,这是语言规范所不允许的)。

即使在编译器可以将值的地址传递给方法的情况下,如果方法修改了该值,则更改也将在调用方中丢失。例如,如果bytes.BufferWrite方法使用值接收器而不是指针,则此代码:

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

会将标准输入复制到buf的副本中,而不是复制到buf本身。这几乎从来不是期望的行为。

作为 goroutines 运行的闭包会发生什么?

当使用并发闭包时,可能会引起一些混乱。考虑以下程序:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

可能会错误地期望看到a、b、c作为输出。您可能会看到的是c、c、c。这是因为循环的每次迭代都使用变量v的相同实例,所以每个闭包共享一个变量。当闭包运行时,它将在执行fmt.Println时打印v的值,但是自从启动goroutine 以来,v 可能已被修改。为了帮助在此问题和其他问题发生之前发现它们,请运行go vet

要将v的当前值绑定到每个闭包启动时,必须修改内部循环以在每次迭代时创建一个新变量。一种方法是将变量作为参数传递给闭包:

 for _, v := range values {
     go func(u string) {
         fmt.Println(u)
         done <- true
     }(v)
 }

在此示例中,v的值作为参数传递给匿名函数。然后可以在函数内部将该值作为变量u进行访问。

甚至更容易的是使用声明样式创建一个新变量,该声明样式可能看起来很奇怪,但在Go中可以正常工作:

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

回想起来,这种语言的行为(未为每次迭代定义新的变量)可能是一个错误。可能会在更高版本中解决,但出于兼容性考虑,在Go版本1中无法更改。