本文中译于官方FAQ
Go项目函数和方法
为什么 T 和 *T 具有不同的方法集?
如Go规范所述,类型T的方法集由接收者类型T的所有方法组成,而对应的指针类型*T的方法集由接收者*T或T的所有方法组成。这意味着*T的方法集包括T的方法集,但是T的方法集不包括*T的方法集。
之所以会出现这种区别,是因为如果接口值包含指针*T,则方法调用可以通过取消引用指针来获取值,但是如果接口值包含值T,则方法调用就没有安全的方法来获取指针(这样做将允许一种方法来修改接口内部值的内容,这是语言规范所不允许的)。
即使在编译器可以将值的地址传递给方法的情况下,如果方法修改了该值,则更改也将在调用方中丢失。例如,如果bytes.Buffer的Write方法使用值接收器而不是指针,则此代码:
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中无法更改。