除了其他人所说的话(尤其是他对彼得回答的 comments 中的PeterSO
和dskinner
),请注意以下几点:
您可以像调用简单函数一样调用方法
在Go中,您可以调用any方法函数,而不是作为接收器上的方法,而是作为常规函数-只需使用定义为方法的类型的名称限定其名称,然后explicitly将其传递给接收器参数(从方法获取简单函数将被调用
使用method expression).
要演示,请执行以下操作:
package main
import "fmt"
type Foo int
func (f Foo) Bar() {
fmt.Printf("My receiver is %v\n", f)
}
func main() {
a := Foo(46)
a.Bar()
b := Foo(51)
Foo.Bar(b)
}
(Playground link.)
运行时,此程序打印:
My receiver is 46
My receiver is 51
正如您所看到的,self
在这里失go 了它的神圣意义,因为您刚刚调用了一个为其构造上下文的方法artificially,该方法与经常引用的"调用对象的方法就是向该对象传递消息"的概念没有任何关系.
总而言之,在Go中,方法只是一个语义上绑定到特定类型的函数,它从接收方接收一个额外的参数,无论如何调用它.与许多其他主流语言相反,围棋并没有掩盖这一事实.
A receiver is not necessarily mutable inside a method defined on its type
如我的示例所示,我已经在一个非指针接收器上定义了一个方法Bar()
,如果您try 将一个值赋给接收器,该值将会成功,但不会影响调用者,因为接收器-就像GO中的所有东西-都是通过值传递的(所以刚刚复制了整数).
为了能够在方法中改变接收器的值,您必须在适当类型的指针上定义它,如
func (f *Foo) Bar() {
// here you can mutate the value via *f, like
*f = 73
}
同样,您可以看到,使用self
表示"我","我的内部 struct "在这里变得毫无意义:在我的示例中,该方法只接收了它所知道的类型的值.您可以看到,这与许多面向对象语言形成对比,在这些语言中,对象是一个黑盒,通常通过引用传递.在Go中,您可以在几乎任何东西(顺便说一句,net/http
标准包使用的是including other methods)上定义一个方法,这会削弱"方法是用于对象"的概念.
不同的方法集可能在不同的时间适用于相同的值
在GO中,方法是围绕特定类型对功能进行分组的便捷方式,不同的方法集可能适用于程序流的不同点中的相同值.与它们提供的接口和鸭子类型相结合,这个概念真正蓬勃发展.其 idea 是,在GO中,有一种习惯用法定义"支持"类型,这些类型对其他类型的值执行特定操作.
standard package sort
就是一个很好的例子:例如,它提供了类型IntSlice
,允许您对一片整数进行排序-一个类型为[]int
的值.要做到这一点,您可以键入-将您的切片转换为sort.IntSlice
,结果是您获得的值有一套完整的方法来对您的切片进行排序,而the internal representation of your value has not changed-因为sort.IntSlice
是defined as type IntSlice []int
.在IntSlice
种类型中每种方法中,很难使它们的接收器值的含义与self
一致-简单地说,因为该类型的存在仅仅是为了为另一种类型提供一组方法;在哲学意义上,这样的效用类型没有"self "的概念;-)
结论
所以我想说,在你的头脑中保持事情的简单,不要试图"过载"Go所采取的清晰而简单的方法,因为Go没有explicitly声明它提供的语义.
再加一个音符.我个人对围棋习语的看法是,围棋最重要的属性是它的实用性(而不是理想主义等等),所以如果你看到一些"感觉"不自然的概念,试着把它设计成这样,大多数时候你会发现为什么这个概念会在你的大脑中"点击",变得自然.(我必须承认,要理解围棋中的方法来理解这个特殊的问题,熟悉C
的工作会很有帮助.)