去接收器方法调用语法混淆

时间:2012-11-09 06:35:52

标签: go

我只是在Effective Go阅读,在Pointers 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来调用。我在这里做错了吗?

2 个答案:

答案 0 :(得分:9)

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

  

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

答案 1 :(得分:3)

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

同时,编译器允许您拨打age(23).String()而不是age(23).Set(10)。在这种情况下,我们使用类型age的非可寻址值。由于说&age(23)无效,因此说(&age(23)).Set(10)无效;编译器不会进行扩展。

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

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

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

; Dr我认为Effective Go中的段落可以使用一些重写(尽管我可能太过啰嗦而无法接受这项工作),但这是正确的。类型为*X的指针实际上可以访问所有X的方法,但“X”无法访问*X。因此,在确定对象是否可以满足给定接口时,*X可以实现任何接口X,但反之则不然。此外,即使已知范围内的X类型的变量在编译时是可寻址的 - 因此编译器可以将其转换为*X - 它将拒绝为此目的这样做界面实现因为这样做可能没有意义。