方法
package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//传统的方法
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) //调用传统的方法
fmt.Println(p.Distance(q)) //通过p调用方法
}
上面的代码里那个附加的参数p,叫做方法的接收器(receiver),早期的面向对象语言留下的遗产将调用一个方法称为“向一个对象发送消息”。
在Go语言中,我们并不会像其它语言那样用this或者self作为接收器;我们可以任意的选择接收器的名字。由于接收器的名字经常会被使用到,所以保持其在方法间传递时的一致性和简短性是不错的主意。这里的建议是可以使用其类型的第一个字母,比如这里使用了Point的首字母p。
可以看到,上面的两个函数调用都是Distance,但是却没有发生冲突。第一个Distance的调用实际上用的是包级别的函数main.Distance,而第二个则是使用刚刚声明的Point,调用的是Point类下声明的Point.Distance方法。
这种p.Distance的表达式叫做选择器,因为他会选择合适的对应p这个对象的Distance方法来执行。选择器也会被用来选择一个struct类型的字段,比如p.X。由于方法和字段都是在同一命名空间,所以如果我们在这里声明一个X方法的话,编译器会报错,因为在调用p.X时会有歧义(译注:这里确实挺奇怪的)。
因为每种类型都有其方法的命名空间,我们在用Distance这个名字的时候,不同的Distance调用指向了不同类型里的Distance方法。
让我们来定义一个Path类型,这个Path代表一个线段的集合,并且也给这个Path定义一个叫Distance的方法。
package main
import "fmt"
import "math"
type Point struct{ X, Y float64 }
//传统的方法
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
type Path []Point //定义一个Path切片类型,代表一个线段
func (path Path) Distance() float64 {
sum := 0.0
for i := range path {
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
return sum
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) //调用传统的方法
fmt.Println(p.Distance(q)) //通过p调用方法
fmt.Println("-------------")
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(perim.Distance())
}
Path是一个命名的slice类型,而不是Point那样的struct类型,然而我们依然可以为它定义方法。在能够给任意类型定义方法这一点上,Go和很多其它的面向对象的语言不太一样。因此在Go语言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行为很方便。我们可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型(译注:这个例子里,底层类型是指[]Point这个slice,Path就是命名类型)不是指针或者interface。
两个Distance方法有不同的类型。他们两个方法之间没有任何关系,尽管Path的Distance方法会在内部调用Point.Distance方法来计算每个连接邻接点的线段的长度。
main函数最后计算三角形的周长.
在上面两个对Distance名字的方法的调用中,编译器会根据方法的名字以及接收器来决定具体调用的是哪一个函数。第一个例子中path[i-1]数组中的类型是Point,因此Point.Distance这个方法被调用;在第二个例子中perim的类型是Path,因此Distance调用的是Path.Distance。
对于一个给定的类型,其内部的方法都必须有唯一的方法名,但是不同的类型却可以有同样的方法名,比如我们这里Point和Path就都有Distance这个名字的方法;所以我们没有必要非在方法名之前加类型名来消除歧义,比如PathDistance。这里我们已经看到了方法比之函数的一些好处:方法名可以简短。