在不少oop语言中都有接口类型,java中的接口以及c++中的虚基类都是接口的实现。golang中的接口概念相似,可是它有本身的特色:java
好比 Java 的 interface 实现须要显示的声明:c++
public class MyWriter implements io.Writer {}
意味着对于接口的实现都须要显示的声明,在代码编写方面有依赖限制,同时须要处理包的依赖。而非侵入式接口只需实现其包含的方法便可:golang
type IO struct {} func (io *IO) Read(p []byte) (n int, err error) {...} func (io *IO) Write(p []byte) (n int, err error) {...} // io package type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer }
这种写法很方便,不用引入包依赖。interface底层实现的时候会动态的检测。但也会引入一些问题: 1.性能降低。使用interface做为函数参数,runtime 的时候会动态的肯定行为。使用具体类型则会在编译期就肯定类型。 2.不能清楚的看出struct实现了哪些接口,须要借助ide或其它工具。
ducktype(鸭子类型)意思即为,“看起来像鸭子,走起来像鸭子,叫起来像鸭子即认为是鸭子”,若是一个struct实现了接口中的全部方法,那么它的行为就是这个接口认定的,那么它就是这个接口类型。上文中的 IO 实现了 Reader中方法,那么它就是一个 Reader 类型。编程
从编译角度来看,golang并不支持泛型编程。但能够借助 ducktype 实现语义上的泛型。但仍是限制在接口类型的范围以内,更普遍可用 interface{} 来替换参数,而实现泛型。数组
type IO struct {} func (io *IO) Read(p []byte) (n int, err error) {...} func (io *IO) Write(p []byte) (n int, err error) {...} // io package type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer } func Print(reader Reader) { 。。。 } func Print2(v interface{}) { 。。。 }
任意类型只要实现了 Reader 便可被做为参数传入 func Print(reader Reader)。由于任意类型都实现了空接口,func Print2(v interface{}) 能够接受任意类型的传入。ide
用接口类型做为函数返回值,能够隐藏返回的具体类型。获得的返回值只能依据接口提供的方法执行操做而不用关心或不能看到实际类型的实现细节。函数
在runtime中的实现中有两种接口类型对应:eface(空接口)和 ifcace (非空接口)工具
type iface struct { tab *itab data unsafe.Pointer } type eface struct { _type *_type data unsafe.Pointer } type itab struct { inter *interfacetype _type *_type hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. } type _type struct { size uintptr ptrdata uintptr // size of memory prefix holding all pointers hash uint32 tflag tflag align uint8 fieldalign uint8 kind uint8 alg *typeAlg // gcdata stores the GC type data for the garbage collector. // If the KindGCProg bit is set in kind, gcdata is a GC program. // Otherwise it is a ptrmask bitmap. See mbitmap.go for details. gcdata *byte str nameOff ptrToThis typeOff } type imethod struct { name nameOff ityp typeOff } type interfacetype struct { typ _type pkgpath name mhdr []imethod }
实现对应的struct如上,eface和iface从内存布局上都是type point + data point,type point 指向类型信息,data point 指向内存中的实际数据。oop
func itabAdd(m *itab) { // Bugs can lead to calling this while mallocing is set, // typically because this is called while panicing. // Crash reliably, rather than only when we need to grow // the hash table. if getg().m.mallocing != 0 { throw("malloc deadlock") } t := itabTable if t.count >= 3*(t.size/4) { // 75% load factor // Grow hash table. // t2 = new(itabTableType) + some additional entries // We lie and tell malloc we want pointer-free memory because // all the pointed-to values are not in the heap. t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true)) t2.size = t.size * 2 // Copy over entries. // Note: while copying, other threads may look for an itab and // fail to find it. That's ok, they will then try to get the itab lock // and as a consequence wait until this copying is complete. iterate_itabs(t2.add) if t2.count != t.count { throw("mismatched count during itab table copy") } // Publish new hash table. Use an atomic write: see comment in getitab. atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2)) // Adopt the new table as our own. t = itabTable // Note: the old table can be GC'ed here. } t.add(m) }
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) { t := tab._type if raceenabled { raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2I)) } if msanenabled { msanread(elem, t.size) } x := mallocgc(t.size, t, true) typedmemmove(t, x, elem) i.tab = tab i.data = x return } func convT2E(t *_type, elem unsafe.Pointer) (e eface) { if raceenabled { raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E)) } if msanenabled { msanread(elem, t.size) } x := mallocgc(t.size, t, true) // TODO: We allocate a zeroed object only to overwrite it with actual data. // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice. typedmemmove(t, x, elem) e._type = t e.data = x return }
经过 iface 中的 tab 内的 interfacetype 中的 mhdr 便可获取类型实现的函数列表,只要该列表包含全部的接口声明函数,则认为该类型实现了该接口。由于对函数列表已经进行排序,因此检查时间复杂度为 O(m+n).布局
golang中的赋值操做皆为值传递,对于interface的赋值操做也不例外。
type IO struct {} func (io *IO) Read(p []byte) (n int, err error) {...} func (io *IO) Write(p []byte) (n int, err error) {...} // io package type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader Writer } var reader Reader io := IO{} reader = io //reader保持一份 io 的副本 reader = &io //reader保持 io 的指针值的副本