1、一般调用
类型方法的一般调用方式:
TypeInstanceName.MethodName(ParamList)
- TypeInstanceName:类型实例名或指向实例的指针变量名
- MethodName:类型方法名
- ParamList:方法实参
type T struct {
a int
}
func (t T) Get() int {
return t.a
}
func (t *T) Set(i int) {
t.a = i
}
var t = &T{}
//普通方法调用
t.Set(2)
//普通方法调用
t.Get()
2、方法值
变量x的静态类型是T,M是类型T的一个方法,x.T被称为方法值(method value)。x.T是一个函数类型变量,可以赋值给其他变量,并像普通的函数名一样使用。例如:
f := x.M
f (args...)
//等价于
x.M(args ...)
方法值(method value)其实是一个带有闭包的函数变量,其底层实现原理和带有闭包的匿名函数类似,接收值被隐式地绑定到方法值(method value)的闭包环境中。后续调用不需要再显式地传递接收者。例如:
package main
import "fmt"
func main() {
var t = &T{}
//method value
f := t.Set
//方法值调用
f(2)
t.Print() //结果为0xc42001c070, &{2}, 2
//方法值调用
f(3)
t.Print() //结果为0xc42001c070, &{3}, 3
}
type T struct {
a int
}
func (t T) Get() int {
return t.a
}
func (t *T) Set(i int) {
t.a = i
}
func (t *T) Print() {
fmt.Printf("%p, %v, %d \n", t, t, t.a)
}
3、方法表达式(method expression)
方法表达式相当于提供一种语法将类型方法调用显式地转换为函数调用,接收者(receiver)必须显式地传递进去。下面定义一个类型T,增加两个方法,方法Gt的接收者为T,方法Set的接收者类型为*T。
type T struct {
a int
}
func (t T) Get() int {
return t.a
}
func (t *T) Set(i int) {
t.a = i
}
func (t *T) Print() {
fmt.Printf("%p, %v, %d \n", t, t, t.a)
}
表达式T.Get
和(*T).Set
被称为方法表达式(method expression),方法表达式可以看作函数名,只不过这个函数的首个参数是接收者的实例或指针。T.Get的函数签名是func(t T) int
,(*T).Set的函数签名是func(t*T, int)
。注意:这里的T.Get不能写成(*T).Get,(*T).Set也不能写成T.Set,在方法表达式中编译器不会做自动转换。例如:
//如下方法表达式调用都是等价的
t := T{a: 1}
//普通方法调用
t.Get()
//方法表达式调用
(T).Get(t)
//方法表达式调用
f1 := T.Get; f1(t)
//方法表达式调用
f2 := (T).Get; f2(t)
//如下方法表达式调用都是等价的
(*T).Set(&t, 1)
f3 := (*T).Set; f3(&t, 1)
4、方法集(method set)
命名类型方法接收者有两种类型,一个是值类型,另一个是指针类型,这个和函数是一样的,前者的形参是值类型,后者的形参是指针类型。无论接收者是什么类型,方法和函数的实参传递都是值拷贝。如果接收者是值类型,则传递的是值的副本;如果接收者是指针类型,则传递的是指针的副本。
package main
import "fmt"
func main() {
var a Int = 10
var b Int = 20
c := a.Max(b)
c.Print() //value=50
(&c).Print() //value=50,内部被编译器转换为c.Print()
a.Set(20) //内部被编译器转化为(&a).Set(20)
a.Print() //value=20
(&a).Set(30)
a.Print() //value=30
}
type Int int
func (a Int) Max(b Int) Int {
if a >= b {
return a
} else {
return b
}
}
func (i *Int) Set(a Int) {
*i = a
}
func (i Int) Print() {
fmt.Printf("value=%d\n", i)
}
上面示例定义了一个新类型Int,新类型的底层类型是int,Int虽然不能继承int的方法,但底层类型支持的操作(算术运算和赋值运算)可以被上层类型继承,这是Go类型系统的一个特点。
接收者是Int类型的方法集合(method set):
func (i Int)Print()
func (a Int)Max(b Int)Int
接收者是*Int类型的方法集合(method set):
func (i *Int)Set(a Int)
为了简化描述,将接收者(receiver)为值类型T的方法的集合记录为S,将接收者(receiver)为指针类型T的方法的集合统称为S。类型的方法集总结如下:
- T类型的方法集是S。
- T类型的方法集是S和S。
从上面的示例可以看出,在直接使用类型实例调用类型的方法时,无论值类型变量还是指针类型变量,都可以调用类型的所有方法,原因是编译器在编译期间能够识别出这种调用关系,做了自动的转换。比如a.Set()使用值类型实例调用指针接收者方法,编译器会自动将其转换为(&a).Set(),(&a),Print()使用指针类型实例调用值类型接收者方法,编译器自动将其转化为a.Print()。
5、值调用和表达式调用的方法集
具体类型实例变量直接调用其方法时,编译器会所调用方法进行自动转换,即使接收者是指针的方法,仍然可以使用值类型变量进行调用。
下面讨论在以下两种情况下编译器是否会进行方法的自动转换。
- 通过类型字面量显式地进行值调用和表达式调用,可以看到在这种情况下编译器不会做自动转换,会进行严格的方法集检查。例如:
type Data struct{}
func (Data)Testvalue(){}
func (*Data)TestPointer(){}
//这种字面量显式调用,无论值调用,还是表达式调用,
//编译器都不会进行方法集的自动转换,编译器会严格校验方法集
//*Data方法集是TestPointer和TestValue
//Data方法集只有TestValue
(*Data)(&struct{}{}).TestPointer()//显式的调用
(*Data)(&struct{}{}).TestValue()//显式的调用
(Data)(struct{}(}).Testvalue()//method value
Data.Testvalue(struct{}{})//method expression
//如下调用因为方法集和不匹配而失败
//Data.TestPoiter(struct(]{}) //type Data has no method TestPoiter
//(Data)(struct(){}).TestPointer() //cannot call pointer method on Data(struct (literal)
- 通过类型变量进行值调用和表达式调用,在这种情况下,使用值调用(method value)方式调用时编译器会进行自动转换,使用表达式调用(method expression)方式调用时编译器不会进行转换,会进行严格的方法集检查。例如:
type Data struct{}
func (Data)Testvalue(){}
func (*Data)TestPointer(){}
//声明一个类型变量a
var a Data struct{)(}
//表达式调用编译器不会进行自动转换
Data.Testvalue(a)
//Data.Testvalue(&a)
(*Data).TestPointer(&a)
//Data.TestPointer(&a) //type Data has no method TestPointer
//值调用编译器会进行自动转换
f := a.TestValue
f()
y:= (&a).TestValue//编译器帮助转换a.TestValue
y()
g:= a.TestPointer//会转换为(&a).TestPointer
9()
x := (&a).TestPointer
x()