16.方法

跟结构体绑定的函数, 称为方法

1
2
3
4
type fire struct{}

func (f fire) Play(){
}

Java 中对象里使用 this , python 对象里使用 self , 而 golang 建议可以使用其类型的第一个字母,比如这里使用了Point的首字母p。可以任意选择变量名

方法不能与结构体的属性重名, 编译器会报错

当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数实在太大我们希望能够避免进行这种默认的拷贝,这种情况下我们就需要用到指针了。对应到我们这里用来更新接收器的对象的方法,当这个接受者变量本身比较大时,我们就可以用其指针而不是对象来声明方法

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

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

在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如下面这个例子:

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

如果方法使用指针作为接收器, 那么要提供一个该类型的指针进行调用方法, 幸运的是 , 如果接收器 p 是point 类型变量 , 编译器会隐式的用 &p.ScaleBy(2) 方式调用, 如果接收器 p 是变量, 方法的接收器是指针, 则会隐式的用 *p 去调用

1
p.ScaleBy(2)

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

1
Point{1, 2}.ScaleBy(2)   // 编译错误

但是我们可以用一个*Point这样的接收器来调用Point的方法,因为我们可以通过地址来找到这个变量,只要用解引用符号*来取到该变量即可。编译器在这里也会给我们隐式地插入*这个操作符,所以下面这两种写法等价的:

1
2
pptr.Distance(q)
(*pptr).Distance(q)

声明 method 时, 如何选择使用 值类型还是指针类型的receiver , 需要考虑改变量的大小, 使用值类型会拷贝对象 , 变量过大时会占用较多内存 , 使用指针类型可以直接修改原对象

nil 是合法的接收器类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// An IntList is a linked list of integers.
// A nil *IntList represents the empty list.
type IntList struct {
    Value int
    Tail  *IntList
}
// Sum returns the sum of the list elements.
func (list *IntList) Sum() int {
    if list == nil {
        return 0
    }
    return list.Value + list.Tail.Sum()
}

由于url.Values是一个map类型,并且间接引用了其key/value对,因此url.Values.Add对这个map里的元素做任何的更新、删除操作对调用方都是可见的。

实际上,就像在普通函数中一样,虽然可以通过引用来操作内部值,但在方法想要修改引用本身时是不会影响原始值的,比如把他置换为nil,或者让这个引用指向了其它的对象,调用方都不会受影响。

注意, 这里指的是修改引用本身, 而非引用指向的地址

1
2
var kit *int
func (k *kit) reset(){ k=nil}  // 选择器是值拷贝,不影响对象本身

嵌入结构体 , 匿名属性

1
2
3
4
5
6
type Point struct{ X, Y float64 }

type ColoredPoint struct {
    Point
    Color color.RGBA
}

对于Point中的方法我们也有类似的用法,我们可以把ColoredPoint类型当作接收器来调用Point里的方法,即使ColoredPoint里没有声明这些方法,当然了,在Point类的方法里,你是访问不到ColoredPoint的任何字段的。

小技巧

将包级别变量, sync和 mapping 放到struct 内

1
2
3
4
5
6
var cache = struct {
    sync.Mutex    // name   Mutex
    mapping map[string]string	  // name  Mapping
}{
    mapping: make(map[string]string),
}

**方法值与延时执行 **AfterFunc

1
2
3
4
type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })

直接用 方法"值"传入 , 省掉了上面例子里的匿名函数。

1
time.AfterFunc(10 * time.Second, r.Launch)

方法表达式

当T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数“值”,这种函数会将其第一个参数用作接收器,所以可以用通常(译注:不写选择器)的方式来对其进行调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
p := Point{1, 2}
q := Point{4, 6}

distance := Point.Distance   // method expression
fmt.Println(distance(p, q))  // "5"
fmt.Printf("%T\n", distance) // "func(Point, Point) float64"

scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p)            // "{2 4}"
fmt.Printf("%T\n", scale) // "func(*Point, float64)"

当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了。你可以根据选择来调用接收器各不相同的方法。

示例 set

Go语言里的 set 一般会用map[T]bool这种形式来表示 , 用map类型来表示虽然非常灵活,但我们可以以一种更好的形式来表示它

 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
32
33
// An IntSet is a set of small non-negative integers.
// Its zero value represents the empty set.
type IntSet struct {
    words []uint64
}

//	set 中是否包含 x
func (s *IntSet) Has(x int) bool {
    word, bit := x/64, uint(x%64)
    return word < len(s.words) && s.words[word]&(1<<bit) != 0
}

// 将 x 添加到 set
func (s *IntSet) Add(x int) {
  	// 每一个字都有64个二进制位,用 x/64的商作为字的下标
  	// 用x%64得到的值作为这个字内的bit的所在位置
    word, bit := x/64, uint(x%64)
    for word >= len(s.words) {
        s.words = append(s.words, 0)
    }
    s.words[word] |= 1 << bit
}

// UnionWith sets s to the union of s and t.
func (s *IntSet) UnionWith(t *IntSet) {
    for i, tword := range t.words {
        if i < len(s.words) {
            s.words[i] |= tword
        } else {
            s.words = append(s.words, tword)
        }
    }
}
  1. x 的 string 方法选择器是指针 , 第一个取址打印正常
  2. 第二个编译器会自动加上 &x.string()
  3. 第三个是对象, 没有绑定要 string 方法,
1
2
3
fmt.Println(&x)         // "{1 9 42 144}"
fmt.Println(x.String()) // "{1 9 42 144}"
fmt.Println(x)          // "{[4398046511618 0 65536]}"
Licensed under CC BY-NC-SA 4.0
本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy