Golang中接收者方法語法糖的使用方法詳解
1、概述
在《Golang常用語法糖》這篇博文中我們講解Golang中常用的12種語法糖,在本文我們主要講解下接收者方法語法糖。
在介紹Golang接收者方法語法糖前,先簡單說下Go 語言的指針 (Pointer),大致上理解如下:
- 變量名前的 & 符號,是取變量的內(nèi)存地址,不是取值;
- 數(shù)據(jù)類型前的 * 符號,代表要儲存的是對應數(shù)據(jù)類型的內(nèi)存地址,不是存值;
- 變量名前的 * 符號,代表從內(nèi)存地址中取值 (Dereferencing)。
注意 1:golang 指針詳細介紹請參見《Golang指針隱式間接引用》此篇博文。
2、接收者方法語法糖
在 Go 中,對于自定義類型 T,為它定義方法時,其接收者可以是類型 T 本身,也可能是 T 類型的指針 *T。
type Instance struct{}
func (ins *Instance) Foo() string {
return ""
}
在上例中,我們定義了 Instance 的 Foo 方法時,其接收者是一個指針類型(*Instance)。
func main() {
var _ = Instance{}.Foo() //編譯錯誤:cannot call pointer method on Instance{} ,變量是不可變的(該變量沒有地址,不能對其進行尋址操作)
}因此,如果我們用 Instance 類型本身 Instance{} 值去調(diào)用 Foo 方法,將會得到以上錯誤。
type Instance struct{}
func (ins Instance) Foo() string {
return ""
}
func main() {
var _ = Instance{}.Foo() // 編譯通過
}
此時,如果我們將 Foo 方法的接收者改為 Instance 類型,就沒有問題。
這說明,定義類型 T 的函數(shù)方法時,其接收者類型決定了之后什么樣的類型對象能去調(diào)用該函數(shù)方法。但,實際上真的是這樣嗎?
type Instance struct{}
func (ins *Instance) String() string {
return ""
}
func main() {
var ins Instance
_ = ins.String() // 編譯器會自動獲取 ins 的地址并將其轉(zhuǎn)換為指向 Instance 類型的指針_ = (&ins).String()
}
實際上,即使是我們在實現(xiàn) Foo 方法時的接收者是指針類型,上面 ins 調(diào)用的使用依然沒有問題。
Ins 值屬于 Instance 類型,而非 *Instance,卻能調(diào)用 Foo 方法,這是為什么呢?這其實就是 Go 編譯器提供的語法糖!
當一個變量可變時(也就是說,該變量是一個具有地址的變量,我們可以對其進行尋址操作),我們對類型 T 的變量直接調(diào)用 *T 方法是合法的,因為 Go 編譯器隱式地獲取了它的地址。變量可變意味著變量可尋址,因此,上文提到的 Instance{}.Foo() 會得到編譯錯誤,就在于 Instance{} 值不能尋址。
注意 1:在 Go 中,即使變量沒有被顯式初始化,編譯器仍會為其分配內(nèi)存空間,因此變量仍然具有內(nèi)存地址。不過,由于變量沒有被初始化,它們在分配后僅被賦予其類型的默認零值,而不是初始值。當然,這些默認值也是存儲在變量分配的內(nèi)存空間中的。
例如,下面的代碼定義了一個整型變量 x,它沒有被顯式初始化,但是在分配內(nèi)存時仍然具有一個地址:
var x int
fmt.Printf("%p\n", &x) // 輸出變量 x 的內(nèi)存地址輸出結果類似于:0xc0000120a0,表明變量 x 的內(nèi)存地址已經(jīng)被分配了。但是由于變量沒有被初始化,x 的值將為整型的默認值 0?! ?/p>
3、深入測試
3.1 示例
package main
type B struct {
Id int
}
func New() B {
return B{}
}
func New2() *B {
return &B{}
}
func (b *B) Hello() {
return
}
func (b B) World() {
return
}
func main() {
// 方法的接收器為 *T 類型
New().Hello() // 編譯不通過
b1 := New()
b1.Hello() // 編譯通過
b2 := B{}
b2.Hello() // 編譯通過
(B{}).Hello() // 編譯不通過
B{}.Hello() // 編譯不通過
New2().Hello() // 編譯通過
b3 := New2()
b3.Hello() // 編譯通過
b4 := &B{} // 編譯通過
b4.Hello() // 編譯通過
(&B{}).Hello() // 編譯通過
// 方法的接收器為 T 類型
New().World() // 編譯通過
b5 := New()
b5.World() // 編譯通過
b6 := B{}
b6.World() // 編譯通過
(B{}).World() // 編譯通過
B{}.World() // 編譯通過
New2().World() // 編譯通過
b7 := New2()
b7.World() // 編譯通過
b8 := &B{} // 編譯通過
b8.World() // 編譯通過
(&B{}).World() // 編譯通過
}
輸出結果:
./main.go:25:10: cannot call pointer method on New()
./main.go:25:10: cannot take the address of New()
./main.go:33:10: cannot call pointer method on B literal
./main.go:33:10: cannot take the address of B literal
./main.go:34:8: cannot call pointer method on B literal
./main.go:34:8: cannot take the address of B literal
3.2 問題總結
假設 T 類型的方法上接收器既有 T 類型的,又有 *T 指針類型的,那么就不可以在不能尋址的 T 值上調(diào)用 *T 接收器的方法
- &B{} 是指針,可尋址
- B{} 是值,不可尋址
- b := B{} b是變量,可尋址
4、總結
在 Golang 中,當一個變量是可變的(也就是說,該變量是一個具有地址的變量,我們可以對其進行尋址操作),我們可以通過對該變量的指針進行方法調(diào)用來執(zhí)行對該變量的操作,否則就會導致編譯錯誤。
到此這篇關于Golang中接收者方法語法糖的使用方法詳解的文章就介紹到這了,更多相關Golang語法糖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

