type T struct {} func (t T) Value() {} //value receiver func (t *T) Pointer() {} //pointer receiver
思考題:下面哪些語句在運行時會報錯?
func main() { var p *T p.Pointer() (*T)(nil).Pointer() (*T).Pointer(nil) p.Value() }
另外,map中的元素是不可尋址的(not addressable),簡單來說就是不能取指針。所以如果map中存儲struct元素的話,大部分情況都是以指針類型定義的。
func main() { m := make(map[string]T, 0) m["a"] = T{} m["a"].Value() // GOOD m["a"].Pointer() // BAD,編譯錯誤 } ----------------------------------------- func main() { m := make(map[string]*T, 0) m["a"] = T{} m["a"].Value() // GOOD m["a"].Pointer() // GOOD }
可以通過反射進行驗證:
func printMethodSet(obj interface{}) { t := reflect.TypeOf(obj) for i, n := 0, t.NumMethod(); i < n; i++ { m := t.Method(i) fmt.Println(t, m.Name, m.Type) } } func main() { var t T printMethodSet(t) fmt.Println("----------------") printMethodSet(&t) }
輸出結果:
main.T Value func(main.T) ---------------- *main.T Pointer func(*main.T) *main.T Value func(*main.T)
可以看到,*T類型包含了receiver T + *T的方法。但是,似乎Value()方法的receiver被改變了?
敲黑板:方法集僅僅用來驗證接口實現,對象或對象指針會直接調用原實現,不會使用方法集
思考題:下面程序的輸出是什麼?
type T struct { x int } func (t T) Value() { //value receiver t.x++ } func (t *T) Pointer() { //pointer receiver t.x++ //Go沒有->運算符,編譯器會自動把t轉成(*t) } func main() { var t *T = &T{1} t.Value() fmt.Println(t.x) t.Pointer() fmt.Println(t.x) }
先看一下Go語言的實現,代碼位於runtime/runtime2.go:
type iface struct { tab *itab //類型信息 data unsafe.Pointer //實際對象指針 } type itab struct { inter *interfacetype //接口類型 _type *_type //實際對象類型 hash uint32 _ [4]byte fun [1]uintptr //實際對象方法地址 }
可以看到,interface其實就是兩個指針,一個指向類型信息,一個指向實際的對象。
對象方法查找的兩大陣營:
Go採取了一種獨有(折衷)的實現方式:
舉例:
1 type I interface { 2 hello() 3 } 4 5 type S struct { 6 x int 7 } 8 func (S) hello() {} 9 10 func main() { 11 s := S{1} 12 var iter I = s 13 for i := 0; i < 100; i++ { 14 iter.hello() 15 } 16 }
Go會在第12行完成itable的計算,然後在第14行直接跳轉。而在Python中則要到第14行才進行方法查找,雖然有cache的存在,仍然比直接一條跳轉指令低效得多。
可以用gdb查看接口內部數據。先用下面的命令阻止編譯器優化:
go build -gcflags 「-N -l」
從下面的例子可以看出,s的地址和i.data不同,發生了對象複製:
type I interface { hello() } type S struct { x int } func (S) hello() {} func main() { s := S{100} var i I = s i.hello() } ================= gdb調試信息 ======================= (gdb) i locals i = {tab = 0x1071dc0 <S,main.I>, data = 0xc420012098} s = {x = 100} (gdb) p/x &s $1 = 0xc420041f58
而下面這個例子中是指針賦值,因此s的地址和i.data是相同的。
type I interface { hello() } type S struct { x int } func (*S) hello() {} func main() { s := S{100} var i I = &s i.hello() } ================= gdb調試信息 ======================= (gdb) i locals &s = 0xc420076000 i = {tab = 0x1071cc0 <S,main.I>, data = 0xc420076000}
常見錯誤:
type MyError struct{} func (*MyError) Error() string { return "myerror" } func isPositive(x int) (int, error) { var err *MyError if (x <= 0) { err = new(MyError) return -x, err } return x, err //注意,err是有類型的! } func main() { _, err := isPositive(100) if err != nil { fmt.Println("ERROR!") } }
可以看到,isPositive()函數返回err時相當於進行了一次類型轉換,把*MyError對象轉換爲一個error接口。這個接口變量的data指針爲nil,但itab指針不爲空,指向MyError類型。
正確做法:直接返回nil即可
看下面的例子:
func print(names []interface{}) { for _, n := range names { fmt.Println(n) } } func main() { names := []string {"star", "jivin", "sheng"} print(names) }
編譯後會報以下錯誤:
cannot use names (type []string) as type []interface {} in argument to print
原因解釋:[]interface{}在編譯時就有確定的內存佈局,每個元素的大小是固定的(2個指針),而[]string的內存佈局顯然不同。至於爲什麼Go爲什麼不幫我們做這個轉換,個人猜測可能是因爲轉換的開銷比較大。
解決方案1: 使用interface{}代替[]interface{}作爲參數
func print(names interface{}) { ns := names.([]string) for _, n := range ns { fmt.Println(n) } }
解決方案2:手動做一次類型轉換
func main() { inames := make([]interface{}, len(names)) for i, n := range names { inames[i] = n } print(inames) }
參考:
https://github.com/golang/go/wiki/MethodSets
https://research.swtch.com/interfaces
https://github.com/golang/go/wiki/InterfaceSlice
更多文章歡迎關注「鑫鑫點燈」專欄:https://blog.csdn.net/turkeycock
或關注飛久微信公衆號: