4

我只是在阅读Effective Go指针与值部分,接近结尾处说:

接收者的指针与值的规则是值方法可以在指针和值上调用,但指针方法只能在指针上调用。这是因为指针方法可以修改接收者;在值的副本上调用它们会导致这些修改被丢弃。

为了测试它,我写了这个:

package main

import (
  "fmt"
  "reflect"
)

type age int

func (a age) String() string {
  return fmt.Sprintf("%d yeasr(s) old", int(a))
}

func (a *age) Set(newAge int) {
  if newAge >= 0 {
    *a = age(newAge)
  }
}

func main() {
  var vAge age = 5
  pAge := new(age)

  fmt.Printf("TypeOf =>\n\tvAge: %v\n\tpAge: %v\n", reflect.TypeOf(vAge),
    reflect.TypeOf(pAge))

  fmt.Printf("vAge.String(): %v\n", vAge.String())
  fmt.Printf("vAge.Set(10)\n")
  vAge.Set(10)
  fmt.Printf("vAge.String(): %v\n", vAge.String())

  fmt.Printf("pAge.String(): %v\n", pAge.String())
  fmt.Printf("pAge.Set(10)\n")
  pAge.Set(10)
  fmt.Printf("pAge.String(): %v\n", pAge.String())
}

它编译,即使文档说它不应该编译,因为指针方法Set()不应该通过值 var 调用vAge。我在这里做错了吗?

4

2 回答 2

9

这是有效的,因为vAge是可寻址的。请参阅语言规范下的调用中的最后一段:

如果 x 的(类型)的方法集包含 m 并且参数列表可以分配给 m 的参数列表,则方法调用 xm() 是有效的。如果 x 是可寻址的并且 &x 的方法集包含 m,则 xm() 是 (&x).m() 的简写。

于 2012-11-09T06:49:49.553 回答
3

vAge不仅仅被视为“值变量”,因为它是内存中存储类型值的已知位置age。只看vAge它的值,作为一个表达式本身vAge.Set(10)无效的,但由于是可寻址的,规范声明可以在编译时vAge将表达式视为“获取 vAge 的地址,并在其上调用 Set”的简写-time,当我们能够验证这Set是为ageor设置的方法的一部分时*age。如果编译器确定它是必要且可能的,则基本上允许编译器对原始表达式进行文本扩展。

同时,编译器将允许您调用age(23).String()但不允许调用age(23).Set(10). 在这种情况下,我们正在使用 type 的不可寻址值age。既然说是无效的&age(23),那就不能说是有效的(&age(23)).Set(10);编译器不会进行这种扩展。

查看 Effective Go 示例,您并没有直接调用b.Write()我们知道b的完整类型的范围。相反,您正在制作一个临时副本b并尝试将其作为 type 的值传递interface io.Writer()。问题是实现对Printf传入的对象一无所知,只是它承诺知道如何接收Write(),所以它不知道在调用函数之前将abyteSlice转换为 a 。*ByteSlice是否寻址的决定b必须在编译时发生,并且PrintF编译的前提是它的第一个参数将知道如何在Write()不被引用的情况下接收。

您可能会认为,如果系统知道如何获取age指针并将其转换为age值,那么它应该能够执行相反的操作;但是,能够做到这一点并没有什么意义。在 Effective Go 示例中,如果您要传递b而不是&b,您将修改一个在 PrintF 返回后将不再存在的切片,这几乎没有用。在我上面的示例中,获取 value并用 value 覆盖它age实际上是没有意义的。在第一种情况下,编译器停下来询问程序员在交接时她真正想要做什么是有意义的。在后一种情况下,编译器拒绝修改常量值当然是有意义的。2310b

此外,我认为系统不会动态扩展age' 方法设置为*age; 我的疯狂猜测是,指针类型静态地为每个基类型的方法提供了一个方法,它只是取消引用指针并调用基类型的方法。自动执行此操作是安全的,因为按值接收方法中的任何内容都无法更改指针。在另一个方向上,扩展一组要求修改数据的方法并不总是有意义,方法是将它们包装成它们修改的数据很快就会消失的方式。在某些情况下这样做是有意义的,但这需要由程序员明确决定,编译器停止并要求这样做是有意义的。

tl;dr I think that the paragraph in Effective Go could use a bit of rewording (although I'm probably too long-winded to take the job), but it's correct. A pointer of type *X effectively has access to all of X's methods, but 'X' does not have access to *X's. Therefore, when determining whether an object can fulfill a given interface, *X is allowed to fulfill any interface X can, but the converse is not true. Furthermore, even though a variable of type X in scope is known to be addressable at compile-time--so the compiler can convert it to a *X--it will refuse to do so for the purposes of interface fulfillment because doing so may not make sense.

于 2012-11-13T00:13:57.073 回答