方法声明 1 2 3 4 func (receiver *T或T) MethodName(参数列表) (返回值列表) { }
receiver 是方法与类型之间的纽带,也是方法与函数的最大不同。无论 receiver 参数的类型为 *T 还是 T,都把一般声明形式中的 T 叫做 receiver 参数 。如果 t 的类型为 T,那么说这个方法是类型 T 的一个方法,如果 t 的类型为 *T,那么就说这个方法是类型 *T 的一个方法。每个方法只能有一个 receiver 参数。
receiver 部分的参数名不能与方法参数列表中的形参名,以及具名返回值中的变量名存在冲突,必须在这个方法的作用域中具有唯一性。
1 2 3 4 type T struct {}func (t T) M(t string ) { }
receiver 参数的基类型不能是指针类型或接口类型。
1 2 3 4 5 6 7 8 9 type MyInt *int func (r MyInt) String() string { return fmt.Sprintf("%d" , *(*int )(r)) }type MyReader io.Readerfunc (r MyReader) Read(p []byte ) (int , error ) { return r.Read(p) }
方法声明要与 receiver 参数的基类型声明放在同一个包内。 所以不能给原生类型和跨越go包给别的类型申明方法。
receiver 参数的基类型为 T,那么 receiver 参数绑定在 T 上,可以通过 *T 或 T 的变量实例调用该方法:
1 2 3 4 5 6 7 8 9 10 11 12 type T struct {}func (t T) M(n int ) { }func main () { var t T t.M(1 ) p := &T{} p.M(2 ) }
方法本质 Go语言中的方法本质是,一个以方法的 receiver 参数作为第一个参数的普通函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func Get (t T) int { return t.a }func Set (t *T, a int ) int { t.a = a return t.a }var t T t.Get() (&t).Set(1 )var t T T.Get(t) (*T).Set(&t, 1 )
一个以方法的 receiver 参数作为第一个参数的普通函数。调用方式这种直接以类型名 T 调用方法的表达方式,被称为 Method Expression。
Method Expression 有些类似于 C++ 中的静态方法(Static Method),C++ 中的静态方法在使用时,以该 C++ 类的某个对象实例作为第一个参数,而 Go 语言的 Method Expression 在使用时,同样以 receiver 参数所代表的类型实例作为第一个参数。
方法自身的类型就是一个普通函数的类型,甚至可以将它作为右值,赋值给一个函数类型的变量。
1 2 3 4 5 6 7 8 func main () { var t T f1 := (*T).Set f2 := T.Get f1(&t, 3 ) fmt.Println(f2(t)) }
如何选择receiver类型 1 2 func (t T) M1() <=> F1(t T)func (t *T) M2() <=> F2(t *T)
以 T 作为 receiver 参数类型时,M1 方法等价转换为 F1(t T),Go 函数的参数采用的是值拷贝传递,也就是说,F1 函数体中的 t 是 T 类型实例的一个副本。这样,我们在 F1 函数的实现中对参数 t 做任何修改,都只会影响副本,而不会影响到原 T 类型实例。
以 *T 作为 receiver 参数类型时,M2 方法等价转换为 F2(t *T)。传递给 F2 函数的 t 是 T 类型实例的地址, F2 函数体中对参数 t 做的任何修改,都会反映到原 T 类型实例上。
如果 Go 方法要把对 receiver 参数代表的类型实例的修改,反映到原类型实例上,那么应该选择 *T 作为 receiver 参数的类型。
无论是 T 类型实例,还是 *T 类型实例,都既可以调用 receiver 为 T 类型的方法,也可以调用 receiver 为 *T 类型的方法,当 Go 编译器发现类型不一致后会自动转换。使用中可以根据是否允许该方法修改结构体实例的值来决定是用 *T 还是 T 作为 receiver,而不必担心调用方法时该怎么使用。
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 type T struct { a int } func (t T) M1() { t.a = 10 }func (t *T) M2() { t.a = 11 }func main () { var t1 T println (t1.a) t1.M1() println (t1.a) t1.M2() println (t1.a) var t2 = &T{} println (t2.a) t2.M1() println (t2.a) t2.M2() println (t2.a) }
如果 receiver 参数类型的 size 较大,以值拷贝形式传入就会导致较大的性能开销,选择 *T 作为 receiver 类型可能更好些。
方法集合 Go 中任何一个类型都有属于自己的方法集合。方法集合是 Go 类型的一个“属性”。但不是所有类型都有自己的方法,比如 int 类型就没有。所以,对于没有定义方法的 Go 类型,称其拥有空方法集合。方法集合是用来判断一个类型是否实现了某接口类型的唯一手段。
Go 语言规定,*T 类型的方法集合包含所有以 *T 为 receiver 参数类型的方法,以及所有以 T 为 receiver 参数类型的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Interface interface { M1() M2() }type T struct {}func (t T) M1() {}func (t *T) M2() {}func main () { var t T var pt *T var i Interface i = pt i = t }
获取类型的方法集合
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 func GetMethod (i interface {}) { dynTyp := reflect.TypeOf(i) if dynTyp == nil { fmt.Printf("there is no dynamic type\n" ) return } n := dynTyp.NumMethod() if n == 0 { fmt.Printf("%s's method set is empty!\n" , dynTyp) return } fmt.Printf("%s's method set:\n" , dynTyp) for j := 0 ; j < n; j++ { fmt.Println("-" , dynTyp.Method(j).Name) } fmt.Printf("\n" ) }func main () { var t T var pt *T GetMethod(t) GetMethod(pt) }
输出结果:
1 2 3 4 5 6 main.T's method set: - M1 *main.T's method set: - M1 - M2