#《Go 语音圣经(中文版)》

#Panic 异常

#Recover 捕获异常

看不懂。。待看。[1]

#方法

#方法声明

在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"math"
)

/*
@Time : 2021/6/6 18:57
@Author : onns
@File : ch6/geometry/geometry.go
*/

type Point struct{ X, Y float64 }

// traditional function
func 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 type
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,叫做方法的接收器(receiver)。

1
2
3
[18:59:54] onns@Onns ~/Onns/code/go/go-bible/ch6 $ go run geometry/geometry.go
5
5

Go语言中可以任意的选择接收器的名字。由于接收器的名字经常会被使用到,建议是可以使用其类型的第一个字母。

在方法调用过程中,接收器参数一般会在方法名之前出现。

第一个Distance的调用实际上用的是包级别的函数geometry.Distance,而第二个则是使用刚刚声明的Point,调用的是Point类下声明的Point.Distance方法。

这种p.Distance的表达式叫做选择器,因为他会选择合适的对应p这个对象的Distance方法来执行。选择器也会被用来选择一个struct类型的字段,比如p.X。由于方法和字段都是在同一命名空间,所以如果我们在这里声明一个X方法的话,编译器会报错,因为在调用p.X时会有歧义。

1
2
3
4
5
6
7
8
9
10
11
12
// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the 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
}

我们可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型不是指针或者interface

对于一个给定的类型,其内部的方法都必须有唯一的方法名,但是不同的类型却可以有同样的方法名。

#基于指针对象的方法

1
2
3
4
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}

这个方法的名字是(*Point).ScaleBy,这里的括号是必须的,没有括号的话这个表达式可能会被理解为*(Point.ScaleBy)

在现实的程序里,一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。

只有类型Point和指向他们的指针(*Point),才可能是出现在接收器声明里的两种接收器。

为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的:

1
2
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type

如果接收器p是一个Point类型的变量,并且其方法需要一个Point指针作为接收器,我们可以用下面这种简短的写法:

1
p.ScaleBy(2)

编译器会隐式地帮我们用&p去调用ScaleBy这个方法。

不能通过一个无法取到地址的接收器来调用指针方法,比如临时变量的内存地址就无法获取得到:

1
Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal

不管你的 method 的 receiver 是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换。

在声明一个 method 的 receiver 该是指针还是非指针类型时,你需要考虑两方面的因素,第一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为 receiver,那么你一定要注意,这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。


  1. https://github.com/gopl-zh/gopl-zh.github.com/blob/master/ch5/ch5-10.md ↩︎