golang在T和* T上区分方法集的原因是什么?

时间:2014-04-13 15:56:04

标签: methods go

这是学习的最让我困惑的地方。我们都知道T上的方法只会影响T的副本,*T上的方法会影响T上的实际数据。

为什么T上的方法也可以被*T使用,但不允许相反的方法?那么,你能否举一个例子(或理由)说明为什么他们不允许*T使用T方法?

这种设计的优点和缺点是什么?

3 个答案:

答案 0 :(得分:6)

这里有很多答案,但没有人回答为什么会这样。

首先让我们看一下你有一个* T并且想要调用一个采用T的方法。为此,你需要做的就是将* yourT(其中*用于取消引用指针)传递给功能。这是可能的,因为您只是在已知位置复制内存blob。

现在假设你有一个T并想要一个* T.你可能会认为你可以做& yourT并得到它的地址。但生活并非总是如此简单。并不总是需要一个静态地址。


来自the spec

  

对于类型为T的操作数x,地址操作& x生成类型为* T到x的指针。操作数必须是可寻址的,即,变量,指针间接或切片索引操作;或可寻址结构操作数的字段选择器;或者可寻址数组的数组索引操作。作为可寻址性要求的例外,x也可以是(可能带括号的)复合文字。

您可能会问自己为什么会将这些任意限制放在获取内存地址上。每个变量都必须有一些内存地址,对吗?虽然这是事实,但优化可以使这些地址相当短暂。

例如,假设变量位于地图内:

res := TMap["key"].pointerMethod()

在这种情况下,你实际上是在说你想要一个指向内存的指针。这将迫使Go的每个实现以这样的方式实现map,即内存地址保持静态。这将严重限制运行时的内部结构,并使实施者在构建有效映射时自由度降低。

还有其他示例,例如函数返回或接口,但您只需要一个示例来证明无法保证操作。


最重要的是,计算机内存并不简单,虽然你可能只想说地址"但它并不总是那么简单。采用保证静态的地址并不总是可行的。因此,您无法保证T的任何实例都可以转换为指针并传递给指针方法。

答案 1 :(得分:3)

界面上最好的文章之一是" How to use interfaces in Go" Jordan OREILLI

它包括以下示例:

type Animal interface {
    Speak() string
}

type Dog struct {
}

func (d Dog) Speak() string {
    return "Woof!"
}

type Cat struct {
}

func (c *Cat) Speak() string {
    return "Meow!"
}

它解释道:

  

指针类型可以访问其关联值类型的方法,但反之亦然   也就是说,*Dog值可以使用Speak上定义的Dog方法,但正如我们之前看到的,Cat值无法访问定义的Speak方法在*Cat

(反映了你的问题)

  

这可能听起来很神秘,但是当你记住以下内容时它是有意义的: Go中的所有内容都按值传递
  每次调用函数时,您传入的数据都会被复制。   对于带有值接收器的方法,在调用方法时会复制该值。

     

当您了解以下签名的方法时,这一点会更明显:

func (t T) MyMethod(s string) {
    // ...
}
  

func(T, string)类型的函数;方法接收器通过值传递到函数中,就像任何其他参数一样。

     

调用者不会看到在值类型(例如func (d Dog) Speak() { ... })上定义的方法内对接收者所做的任何更改,因为调用者正在确定一个完全独立的Dog值。

(即"按值复制"部分)

  

由于所有内容都是按值传递的,因此*Cat方法无法使用Cat方法的原因应该是显而易见的。任何一个Cat值都可以有任意数量的*Cat指针指向它。如果我们尝试使用*Cat值来调用Cat方法,那么我们从未有过*Cat指针。

     

相反,如果我们在Dog类型上有一个方法,并且我们有一个*Dog指针,我们确切地知道调用此方法时要使用的Dog值< / strong>,因为*Dog指针只指向一个Dog; Go运行时将在必要时取消引用指向其关联的Dog值的指针   也就是说,假定*Dog类型dSpeak和方法Dog,我们可以说d.Speak();我们不需要像其他语言那样说d->Speak()之类的内容。

答案 2 :(得分:1)

您的答案中有关键答案:T上的方法仅影响T的副本。因此,有了这些信息,Go's FAQ的以下摘录应该有助于解决其他问题:

  

来自Go Spec:

     
    

&#34;任何其他命名类型T的方法集由所有方法组成     接收器类型T.相应指针类型* T的方法集     是接收器* T或T的所有方法的集合(也就是说,它也是     包含T)的方法集。&#34;

  
     

如果接口值包含a   指针* T,方法调用可以通过解除引用来获取值   指针,但如果接口值包含值T,则没有   方法调用获取指针的有用方法。

     

即使在编译器可以获取值的地址的情况下也是如此   传递给方法,如果方法修改了更改的值   迷路了。作为一个常见的例子,这段代码:

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

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