0%

Go语言如何选择方法接收器的类型

Go语言中的方法

方法用于给指定数据类型(非内置数据类型)添加新的行为,方法实质上也是函数,与函数不同的是,每个方法包含了一个方法的作用对象参数,即:接收者。定义方法的语法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type user struct{
id int
name string
}

//方法的定义: 接收者位于func关键字和方法名之间
func (u user) GetName() string {
return u.name
}

func main() {
u := user{0, "John"}
u.GetName() //方法的调用
}

为什么说方法实质上也是函数呢?因为它与下面的函数是等价的:

1
2
3
func GetName(u user) string {
return u.name
}

方法的接收者

根据方法的语法,介于func关键字和函数名之间的参数即为方法的接收者。

接收者的类型可以是值类型,也可以是指针类型:

1
2
3
4
5
6
7
func (u user) GetName() string {
return u.name
}

func (u *user) GetId() int {
return u.id
}

接收者实质上也是作为一个参数传递给方法,Go语言中的参数传递方式都是值传递,所以:

  1. 在值类型接收者的方法中,在方法调用时会使用接收者值的一个副本来执行。
  2. 在指针类型的接收者方法中,在方法调用时会使用指针的一个副本来执行。

对于值类型的接收者方法来说,可以用指针来调用,反之亦然:

1
2
3
4
5
var u1 = &user{1, "Alice"}
u1.GetName() //执行时相当于: (*u1).GetName()

var u2 = user{2, "John"}
u2.GetId() //执行时相当于: (&u2).GetId()

如何选择接收者的类型

通常情况下,都将接收者设为指针类型。因为接收者为指针类型,所以对于状态的变更会直接影响到接收者,如果是值类型的接收器的话,则接收者不会受影响。

1
2
3
4
5
6
7
8
9
10
11
func (u *user) SetId(id int) {
u.id = id
}

func (u user) SetName(name string) {
u.name = name
}

var u user
u.SetId(1) //变量u的id会被更改
u.SetName("Tracy") //变量u的name不会被更改

从上面可以看出,如果需要对接收者做更改,需要使用指针类型的接收者。但不做更改的话是不是就可以使用值类型呢?如果是没有状态维护的结构体则可以使用值类型的接收者,如果结构体体积比较大,考虑到副本的拷贝效率,仍然建议使用指针类型的接收者。

1
2
3
4
5
type matcher struct{}

func (m matcher) Match(args ...interface) {
//因为没有状态维护,所以可以使用值接收者
}

对于别名类型,也同样适用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type IntArray []int

func (ia IntArray) Len() int {
return len(ia)
}

func (ia *IntArray) Del(target int) {
for i, v := range *ia {
if v == target {
(*ia) = append((*ia)[:i], (*ia)[i+1:]...)
break
}
}
}

总结

Go语言中的方法可能会对其他语言的开发者来说有点不习惯,但方法的本质就是一个函数,接收者是该函数的第一个参数,从这个角度出发,该用指针接收者还是值接收者,就比较明确了。

欢迎指正!