方法 方法是与对象实例绑定的特殊函数。 方法是面向对象编程的基本概念,用于维护和展示对象的自身状态。对象是内敛的,每个实例都有各自不同的独立特征,以属性和方法来暴露对外通信接口。普通函数则专注于算法流程,通过接收参数来完成特定逻辑运算,并返回最终结果。换句话说,方法是有关联的而函数通常没有。 方法和函数定义语法区别,在于前者有前置实例接收参数,编译器以此确定方法所数类型。 可以为当前包,以及除接口和指针以外的任何类型定义方法。
1 2 3 4 5 6 7 8 9 10 11 type N int func (n N) toString() string { return fmt.Sprintf("%#x" , n) } func main () { var a N = 25 println (a.toString()) }
方法同样不支持重载(overload
)。receiver
参数名没有限制,按惯例会选用简短有意义的名称。如方法内部并不引用实例,可省略参数名,仅保留类型。
1 2 3 4 5 type N int func (N) test() { println ("hi~" ) }
方法可看作特殊的函数,那么receiver
的类型自然可以是基础类型或指针类型。这会关系到调用时对象实例是否被赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type N int func (n N) value() { n++ fmt.Printf("v: %p, %v\n" , &n, n) } func (n *N) pointer() { (*n)++ fmt.Printf("p: %p, %v\n" , n, *n) } func main () { var a N = 25 a.value() a.pointer() fmt.Printf("a: %p, %v\n" , &a, a) }
可使用实例值或指针调用方法,编译器会根据方法receiver
类型自动在基础类型和指针类型间转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { var a N = 25 p := &a a.value() a.pointer() p.value() p.pointer() }
指针类型的receiver
必须时合法指针(包括nil
), 或能获取实例地址。
1 2 3 4 5 6 7 8 9 10 11 type X struct {}func (x *X) test() { println ("hi!" , x) } func main () { var a *X a.test() X{}.test() }
如何选择方法的receiver
类型:
要修改实例状态,用*T
无需修改状态的小对象或固定值,建议用T
大对象建议用*T
, 以减少复制成本。
引用类型、字符串、函数等指针包装对象,直接用T。
若包含Mutex
等同步字段,用*T
,避免因复制造成锁操作无效
其他无法确定的情况,都用*T
匿名字段 可以访问匿名字段成员那样调用其方法,有编译器负责查找。
1 2 3 4 5 6 7 8 9 10 type data struct { sync.Mutex buf [1024 ]byte } func main () { d := data() d.Lock() defer d.Unlock() }
方法也会有同名遮蔽问题。但利用这一特性可实现类似(override
)操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type user struct {}type manager struct { user } fun (user) toString() string { return "user" } func (m manager) toString() string { return m.user.toString() + "; manager" } func main () { var m manager println (m.toString()) println (m.user.toString()) }
尽可能直接访问匿名字段的成员和方法,但他们依然不属于继承关系。
方法集 类型有一个与之相关的方法集,这决定了它是否实现某个接口。
类型T
方法集包含所有receiver T
方法。
类型*T
方法集包含所有recever T
+ *T
方法。
匿名嵌入S
, T
方法集包含所有receiver S
方法。
匿名嵌入*S
, T
方法集包含所有receiver S
+*S
方法。
匿名嵌入S
或*S
, *T
方法集包含所有receiver S
+*S
方法。
可利用反射测试这些规则。
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 type S struct {}type T struct { S } func (S) sVal() {}func (*S) sPtr() {}func (T) tVal() {}func (*T) tPtr() {}func methodSet (a interface {}) { t := reflect.TypeOf(a) for i, n := 0 , t.NumMethod(); i < n; i++ { m := t.Method(i) fmt.Println(m.Name, m.Type) } } func main () { var t T methodSet(t) println ("-------" ) methodSet(&t) }
方法集影响接口实现和方法表达式转换,于通过实例或实例指针调用方法无关。实例并不使用方法集,而是直接调用(或通过隐式字段名). 很显然,匿名字段就是为方法准备的。否则,完全没必要为少写个字段名而大费周折。 面向对象的三大特征”封装”,”继承”和”多态”, Go仅仅实现了部分特征,它更倾向于”组合优于继承“这种思想。将模块分解成相互独立的更小单元,分别处理不同方面的需求,最后以匿名嵌入方式组合到一起,共同实现对外接口。而且其简短一致的调用方式,更是隐藏了内部实现细节。
组合没有父子依赖,不会破坏封装。且整体和局部松耦合,可任意增加来实现实现扩展。各单元持有单一职责,互无关联,实现和维护更加简单。
表达式 方法和函数一样,除直接调用外还可以赋值给变量,或作为参数传递。依照具体引用方式不同,可分为expression
和value
两种状态。 通过类型引用Method expression
会被还原为普通函数央视,receiver是第一参数,调用时须显示传参。至于类型,可以是T
或*T
, 只要目标方法集中即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type N int func (n N) test() { fmt.Printf("test.n: %p, %d" , &n, n) } func main () { var n N = 25 fmt.Printf("main.n: %p, %d\n" , &n, n) f1 := N.test f1(n) f2 := (*N).test f2(&n) }
当然,也可直接以表达式方式调用。
1 2 3 4 5 func main () { var n N = 25 N.test(n) (*N).test(&n) }
基于实例或指针引用的method value
, 参数签名不会改变,依旧按正常方式调用。 但当method value
被赋值给变量或作为参数传递时,会立即计算并复制该方法执行所需的receiver
对象,与其绑定,以便在稍后执行时,能隐式传入receiver
参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type N int func (n N) test() { fmt.Printf("test.n: %p, %v\n" , &n, n) } func main () { var n N = 100 p := &n n++ f1 := n.test n++ f2 := p.test n++ fmt.Printf("main.n: %p, %v\n" , p, n) f1() f2() }
编译器会为method value生成一个包装函数,实现间接调用。至于receiver
复制,和闭包的实现方法基本相同,打包成funcval
, 经由DX
寄存器传递。
当method value
作为参数是,会复制含receiver
在内的整个method value
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func call (m func () ) { m() } func main () { var n N = 100 p := &n fmt.Printf("main.h: %p, %v" , p, n) n++ call(n.test) n++ call(p.test) }
当然,如果目标方法的receiver
是指针类型,那么被复制的仅是指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type N int func (n *N) test() { fmt.Printf("test.n: %p, %v\n" , n, *n) } func main () { var n N = 100 p := &n n++ f1 := n.test n++ f2 := p.test n++ fmt.Printf("main.n: %p, %v\n" , p, n) f1() f2() }
只要receiver参数类型正确,使用nil
同样可以执行。
1 2 3 4 5 6 7 8 9 10 11 type N int func (N) value() {}func (*N) pointer() {}func main () { var p *N p.pointer() (*N)(nil ).pointer() (*N).pointer(nil ) }