这是学习的最让我困惑的地方。我们都知道T
上的方法只会影响T
的副本,*T
上的方法会影响T
上的实际数据。
为什么T
上的方法也可以被*T
使用,但不允许相反的方法?那么,你能否举一个例子(或理由)说明为什么他们不允许*T
使用T
方法?
这种设计的优点和缺点是什么?
答案 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
类型d
值Speak
和方法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本身。这几乎不是 期望的行为。