方法声明
Go 语言中同时有函数和方法。方法就是一个包含了接受者(receiver)的函数,receiver可以是内置类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。
func (r ReceiverType) funcName(parameters) (results)
receiver就是隐式传入的第一实参,receiver类型可以是 T 或 *T。其类型 T 不能是接口或指针。
下面来写我们第一个方法的例子,
package mainimport ( "fmt" "math")func main() { p := Point{1.2, 2.0} q := Point{2.0, 4.0} res := Distance(p, q) //包级别的 Distance 函数 fmt.Println(res) res2 := p.Distance(q) //类型级别的 Distance 方法 fmt.Println(res2)}type Point struct{ X, Y float64 }// traditional functionfunc Distance(p, q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y)}// same thing, but as a method of the Point typefunc (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y)}
上面的代码里那个附加的参数p,叫做方法的接收器(receiver),早期的面向对象语言留下的遗产将调用一个方法称为“向一个对象发送消息”。
在Go语言中,我们并不会像其它语言那样用this或者self作为接收器;我们可以任意的选择接收器的名字。由于接收器的名字经常会被使用到,所以保持其在方法间传递时的一致性和简短性是不错的主意。这里的建议是可以使用其类型的第一个字母,比如这里使用了Point的首字母p。
在方法调用过程中,接收器参数一般会在方法名之前出现。这和方法声明是一样的,都是接收器参数在方法名字之前。下面是例子:
p := Point{1, 2}q := Point{4, 6}fmt.Println(Distance(p, q)) // "5", function callfmt.Println(p.Distance(q)) // "5", method call
可以看到,上面的两个函数调用都是Distance,但是却没有发生冲突。
第一个Distance的调用实际上用的是包级别的函数geometry.Distance,而第二个则是使用刚刚声明的Point,调用的是Point类下声明的Point.Distance方法。
不同的Distance调用指向了不同类型里的Distance方法。让我们来定义一个Path类型,这个Path代表一个线段的集合,并且也给这个Path定义一个叫Distance的方法。
package mainimport ( "fmt" "math")func main() { p := Point{1.2, 2.0} q := Point{2.0, 4.0} res := Distance(p, q) //包级别的 Distance 函数 fmt.Println(res) res2 := p.Distance(q) //类型级别的 Distance 方法 fmt.Println(res2) perim := Path{ {1, 1}, {5, 1}, {5, 4}, {1, 1}, } fmt.Println(perim.Distance()) // "12"}type Point struct{ X, Y float64 }// traditional functionfunc Distance(p, q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y)}// same thing, but as a method of the Point typefunc (p Point) Distance(q Point) float64 { return math.Hypot(q.X-p.X, q.Y-p.Y)}type Path []Pointfunc (path Path) Distance() float64 { sum := 0.0 for i := range path { if i > 0 { sum += path[i-1].Distance(path[i]) } } return sum}
Path是一个命名的slice类型,而不是Point那样的struct类型,然而我们依然可以为它定义方法。在能够给任意类型定义方法这一点上,Go和很多其它的面向对象的语言不太一样。
因此在Go语言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行为很方便。方法可以被声明到任意类型,只要不是一个指针或者一个interface。
两个Distance方法有不同的类型。他们两个方法之间没有任何关系,尽管Path的Distance方法会在内部调用Point.Distance方法来计算每个连接邻接点的线段的长度。
让我们来调用一个新方法,计算三角形的周长:
perim := Path{ {1, 1}, {5, 1}, {5, 4}, {1, 1},}fmt.Println(perim.Distance()) // "12"
在上面两个对Distance名字的方法的调用中,编译器会根据方法的名字以及接收器来决定具体调用的是哪一个函数。第一个例子中path[i-1]数组中的类型是Point,因此Point.Distance这个方法被调用;在第二个例子中perim的类型是Path,因此Distance调用的是Path.Distance。
对于一个给定的类型,其内部的方法都必须有唯一的方法名,但是不同的类型却可以有同样的方法名,比如我们这里Point和Path就都有Distance这个名字的方法;所以我们没有必要非在方法名之前加类型名来消除歧义,比如PathDistance。
Go方法 - 方法集
Golang方法集 :每个类型都有与之关联的方法集。
- 类型 T 方法集包含全部 receiver T 方法。
- 类型 *T 方法集包含全部 receiver T + *T 方法。
- 如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。
- 如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。
- 不管嵌入 T 或 *T,*S 方法集总是包含 T + *T 方法。
用实例 value 和 pointer 调用方法 (含匿名字段) 不受方法集约束,编译器总是查找全部方法,并自动转换 receiver 实参(解引用和取地址)。
Go 语言中内部类型方法集提升的规则:
- 类型 T 方法集包含全部 receiver T 方法。
- 类型 *T 方法集包含全部 receiver T + *T 方法。
给定一个结构体类型 S 和一个命名为 T 的类型,方法提升像下面规定的这样被包含在结构体方法集中:
- 如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。
- 如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。
==========END==========