在不少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 的指針值的副本