Go 方法(第二部分)
這篇文章介紹了關於 Go 語言中方法的剩餘部分。強烈建議先閱讀第一部分 的介紹部分。
方法表示式
如果有這樣一個型別 T,它的方法集中包含方法 M,則 T.M 會生成一個與方法 M 幾乎相同且帶有簽名的方法,這稱為方法表示式 。不同之處在於,它額外附帶的第一個引數與 M 的接收者型別相等。
package main import ( "fmt" "reflect" ) func PrintFunction(val interface{}) { t := reflect.TypeOf(val) fmt.Printf("Is variadic: %v\n", t.IsVariadic()) for i := 0; i < t.NumIn(); i++ { fmt.Printf("Parameter #%v: %v\n", i, t.In(i)) } } type T struct{} func (t T) M(text string, number int) {} func (t *T) N(map[string]int){} func main() { PrintFunction(T.M) PrintFunction((*T).M) PrintFunction((*T).N) }
輸出:
Is variadic: false Parameter #0: main.T Parameter #1: string Parameter #2: int Is variadic: false Parameter #0: *main.T Parameter #1: string Parameter #2: int Is variadic: false Parameter #0: *main.T Parameter #1: map[string]int
如果方法 M 不在型別 T 的方法集中,使用表示式T.M
會導致錯誤invalid method expression T.N (needs pointer receiver: (*T).N)
。
在上面的片段中,有一個有趣的案例PrintFunction((*T).M)
,即使方法 M 擁有的是值接收器,它仍然使用*main.T
的第一個引數建立方法。Go 的執行時會在後臺傳遞指標,建立副本並傳遞給方法。使用這種方式,方法無法訪問原始值。
type T struct { name string } func (t T) M() { t.name = "changed" } func (t *T) N() { t.name = "changed" } func main() { t := T{name: "Michał"} (*T).M(&t) fmt.Println(t.name) (*T).N(&t) fmt.Println(t.name) }
輸出:
Michał changed
可以從介面型別建立方法表示式:
package main import "fmt" type T struct { name string } func (t T) M() { fmt.Println(t.name) } type I interface { M() } func main() { t1 := T{name: "Michał"} t2 := T{name: "Tom"} m := I.M m(t1) m(t2) }
輸出:
Michał Tom
方法值
與型別和方法表示式
類似,使用表示式可以得到一個帶有接收器的方法,這就是方法值
。如果有表示式x
,則x.M
和方法 M 一樣可以使用同樣的引數呼叫。當然,方法 M 需要在型別 x 的方法集中,如果 x 是可定址型別,M 應該在型別&x
的方法集中。
type T struct { name string } func (t *T) M(string) {} func (t T) N(float64) {} func main() { t := T{name: "Michał"} m := t.M n := t.N m("foo") n(1.1) }
提升方法
如果一個結構包含內嵌(匿名)的屬性,那麼這個屬性的方法也處於該結構型別的方法集中。
package main import "fmt" type T struct { name string } func (t T) M() string { return t.name } type U struct { T } func main() { u := U{T{name: "Michał"}} fmt.Println(u.M()) }
上面的 Go 程式輸出Michał
是完全正確的。說嵌入到結構型別中屬性的方法屬於該型別的方法集是有確切原因的:
#1
如果結構型別 U 包含了內嵌屬性 T,那麼方法集 S 和*S
包含帶有接收器 T 的提升方法。另外,方法集*S
包含的是帶有接收器*T
的提升方法。
package main import ( "fmt" "reflect" ) func PrintMethodSet(val interface{}) { t := reflect.TypeOf(val) fmt.Printf("Method set count: %d\n", t.NumMethod()) for i := 0; i < t.NumMethod(); i++ { m := t.Method(i) fmt.Printf("Method: %s\n", m.Name) } } type T struct { name string } func (t T) M(){} func (t *T) N() {} type U struct { T } func main() { u := U{T{name: "Michał"}} PrintMethodSet(u) PrintMethodSet(&u) }
上面的程式輸出:
Method set count: 1 Method: M Method set count: 2 Method: M Method: N
從本文介紹的第一部分,我們應當知曉的是語言規範中的附加呼叫規則:
如果 x 是可定址的,並且 &x 的方法集中包含 m,(&x).m() 可以簡寫為 x.m()。
所以儘管方法 N 不是型別 U 的方法集的一部分,我們仍可以使用u.N()
這樣的呼叫。
#2
如果結構型別 U 包含內嵌屬性*T
,那麼方法集 S 和*S
中包帶有接收器 T 和*T
的提升方法。
type T struct { name string } func (t T) M(){} func (t *T) N() {} type U struct { *T } func main() { u := U{&T{name: "Michał"}} PrintMethodSet(u) PrintMethodSet(&u) }
列印:
Method set count: 2 Method: M Method: N Method set count: 2 Method: M Method: N