在 Go 中,定义结构体有两种接收器可以选择

type T struct {
    a int
}

func (t T) Fv(a int) int {
    fmt.Println("in fv")
    t.a = a
    return a
}
func (t *T) Fp(a int) int {
    fmt.Println("in fp")
    t.a = a
    return a
}

区别和联系

T 和 *T 是两个不同的类型。每个类型都有自己的方法集。

T 的方法集包含了所有以 T 为 receiver 的方法。

*T 的方法集包含了所有以 *T 或 T 为 receiver 的方法。

作为变量直接调用两者都可以,如果修改成员变量效果不一样

当以 T 或 *T 类型的变量作为 receiver 去调用一个方法的时候,不管该方法定义的 receiver 类型是什么,都可以调用。普通方法调用只看类型签名(类名),不看方法集(method_set),但是效果不一样。

func main() {
    tv := T{}
    tv.Fp(1)
    tv.Fv(1)
    tp := &T{}
    tp.Fp(2)
    tp.Fv(2)    
}

// in fp
// in fv
// in fp
// in fv

这里两个类型都可以调用其方法。

func main() {
    tv := T{}
    tv.a = 0
    tv.Fp(1)
    fmt.Println("tv.fp: ", tv.a)

    tv.a = 0
    tv.Fv(1)
    fmt.Println("tv.fv: ", tv.a)

    tp := &T{}
    tp.a = 0
    tp.Fp(1)
    fmt.Println("tp.fp: ", tp.a)
    tp.a = 0
    tp.Fv(1)
    fmt.Println("tp.fv: ", tp.a)
}

// output
in fp
tv.fp:  1
in fv
tv.fv:  0
in fp
tp.fp:  1
in fv
tp.fv:  0

可以看到只有 fp 的方法真正的修改了原始的值。可以这样理解,接收者可以看作是函数的第一个参数,即这样的: func Fv(t T, a int), func Fp(t *T, a int)。所以只有传指针进去的才能修改原始变量的值,传值的都是传的原始变量的副本,修改的也是副本的值,原始变量不会变。

当 T 满足了一个 interface 的时候,*T 也一定会满足这个 interface。反之则不然。

用下面的代码验证

type IFV interface {
    Fv(int) int
}

type IFP interface {
    Fp(int) int
}

func main() {
    var ifv IFV
    var ifp IFp

    ifv = T{}
    ifp = T{}

    ifv = &T{}
    ifp = &T{}
}

// output
//./main.go:30:6: cannot use T literal (type T) as type IFV in assignment:
//        T does not implement IFV (Fv method has pointer receiver)

什么时候使用指针接收器

  • 如果要在使用中更改接收器的状态,修改它的值,就需要使用指针接收器。(对值接收器的任何修改都是对该副本的本地修改)

  • 如果定义方法的结构体非常大,复制它将比使用指针接收器代价更大。

什么时候使用值接收器

  • 如果不需要编辑接收器的值。

  • 值接收器是并发安全的,指针接收器不是的。

参考

https://github.com/golang/go/wiki/CodeReviewComments#receiver-type

什么时候使用指针接收器