思考题:下面哪些语句在运行时会报错?
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其实就是两个指针,一个指向类型信息,一个指向实际的对象。 对象方法查找的两大阵营:
静态类型语言:如C++/Java,在编译时生成完整的方法表动态类型语言:如Python/Javascript,在每次调用方法时进行查找(会使用cache)Go采取了一种独有(折衷)的实现方式:
在进行类型转换时计算itab,查找具体实现itab类型只和interface相关,也就是说只包含接口声明的方法的具体实现(没有多余方法)举例:
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 或关注飞久微信公众号: