【问题标题】:Go receiver methods calling syntax confusionGo 接收器方法调用语法混淆
【发布时间】:2012-11-09 06:35:52
【问题描述】:

我刚刚阅读了Effective GoPointers vs. Values 部分,靠近结束,它说:

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

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

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 调用。我在这里做错了吗?

【问题讨论】:

    标签: go


    【解决方案1】:

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

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

    【讨论】:

    • 我不会说这是错误的。在这种有限的情况下,Go 隐含地获取 x 的地址,因此该方法仍在对指针进行操作。区别在于当您将 vAge 与 &vAge 传递给需要 fmt.Stringer 的函数时。第一个会失败,第二个不会。
    【解决方案2】:

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

    同时,编译器将允许您调用age(23).String(),但不允许调用age(23).Set(10)。在这种情况下,我们正在使用age 类型的不可寻址值。由于说&age(23)无效,所以说(&age(23)).Set(10)也无效;编译器不会进行这种扩展。

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

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

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

    tl;dr 我认为 Effective Go 中的段落可以使用一些改写(尽管我可能太啰嗦了,无法接受这份工作),但它是正确的。 *X 类型的指针实际上可以访问X 的所有方法,但'X' 不能访问*X 的方法。因此,在确定一个对象是否可以实现给定接口时,*X 可以实现任何X 可以实现的接口,但反之则不成立。此外,即使已知作用域内的 X 类型的变量在编译时是可寻址的——因此编译器可以将其转换为 *X——出于接口实现的目的,它会拒绝这样做,因为这样做可能没有意义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-09
      • 2021-12-23
      • 2013-10-26
      相关资源
      最近更新 更多