内容简介:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/TurkeyCock/article/details/83317943
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/TurkeyCock/article/details/83317943
1.鸭子类型(Duck Typing)
- If it walks like a duck and it quacks like a duck, then it must be a duck.
- interface是一种鸭子类型
- 无需显示声明,只要对象实现了接口声明的的全部方法,就实现了该接口
- 把对象的类型检查从编译时推迟到运行时
- 好处:
- 松耦合
- 可以先实现类型,再抽象接口
2.值receiver VS. 指针receiver
type T struct {} func (t T) Value() {} //value receiver func (t *T) Pointer() {} //pointer receiver
- 值receiver会复制对象实例,而指针receiver不会
- 把方法看作普通函数,receiver可以理解为传入的第一个参数
- 只要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 }
3.方法集
- 类型有一个与之相关的方法集,决定了它是否实现某个接口
- 类型T的方法集包含所有receiver T的方法
- 类型*T的方法集包含所有receiver T + *T的方法
可以通过反射进行验证:
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) }
4.什么是interface?
先看一下 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的存在,仍然比直接一条跳转指令低效得多。
5.interface赋值
- 将对象赋值给接口变量时,会复制该对象
- 把指针赋值给接口变量则不会发生复制操作
可以用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}
6.interface何时等于nil?
- 只有当接口变量中的itab和data指针都为nil时,接口才等于nil
常见错误:
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即可
7.空接口interface{}
- interface{}可以接受任意类型,会自动进行转换(类似于 Java 中的Object)
- 例外:接口切片[]interface{}不会自动进行类型转换
看下面的例子:
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
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。