golang中的接口分爲帶方法的接口和空接口。 帶方法的接口在底層用iface表示,空接口的底層則是eface表示。下面咱們透過底層分別看一下這兩種類型的接口原理。html
如下是接口的原型:golang
//runtime/runtime2.go
//非空接口
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized
}
//******************************
//空接口
type eface struct {
_type *_type
data unsafe.Pointer
}
//========================
//這兩個接口共同的字段_type
//========================
//runtime/type.go
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這個結構體是golang定義數據類型要用的,講到反射文章的時候在具體講解這個_type。
複製代碼
看下方代碼:數組
package main
type Person interface {
run()
}
type xitehip struct {
age uint8
}
func (o xitehip)run() {
}
func main() {
var xh Person = xitehip{age:18}
xh.run()
}
複製代碼
xh變量是Person接口類型,那xitehip的struct類型是如何轉換成接口類型的呢? 看一下生成的彙編代碼:bash
0x001d 00029 (main.go:13) PCDATA $2, $0
0x001d 00029 (main.go:13) PCDATA $0, $0
0x001d 00029 (main.go:13) MOVB $0, ""..autotmp_1+39(SP)
0x0022 00034 (main.go:13) MOVB $18, ""..autotmp_1+39(SP)
0x0027 00039 (main.go:13) PCDATA $2, $1
0x0027 00039 (main.go:13) LEAQ go.itab."".xitehip,"".Person(SB), AX
0x002e 00046 (main.go:13) PCDATA $2, $0
0x002e 00046 (main.go:13) MOVQ AX, (SP)
0x0032 00050 (main.go:13) PCDATA $2, $1
0x0032 00050 (main.go:13) LEAQ ""..autotmp_1+39(SP), AX
0x0037 00055 (main.go:13) PCDATA $2, $0
0x0037 00055 (main.go:13) MOVQ AX, 8(SP)
0x003c 00060 (main.go:13) CALL runtime.convT2Inoptr(SB)
0x0041 00065 (main.go:13) MOVQ 16(SP), AX
0x0046 00070 (main.go:13) PCDATA $2, $2
0x0046 00070 (main.go:13) MOVQ 24(SP), CX
複製代碼
從彙編發現有個轉換函數: runtime.convT2Inoptr(SB) 咱們去看一下這個函數的實現:frontend
func convT2Inoptr(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
if raceenabled {
raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2Inoptr))
}
if msanenabled {
msanread(elem, t.size)
}
x := mallocgc(t.size, t, false)//爲elem申請內存
memmove(x, elem, t.size)//將elem所指向的數據賦值到新的內存中
i.tab = tab //設置iface的tab
i.data = x //設置iface的data
return
}
複製代碼
從以上實現咱們發現編譯器生成的struct原始數據會複製一份,而後將新的數據地址賦值給iface.data從而生成了完整的iface,這樣以下原始代碼中的xh就轉換成了Person接口類型。函數
var xh Person = xitehip{age:18}
複製代碼
用gdb實際運行看一下(見圖1): ui
convT2Inoptr函數傳進來的參數是*itab和源碼中的 *xitehip。 圖2是itab的類型原型和內存中的數據發現itab確實是runtime中源碼裏的字段。總共佔了32個字節。([4]uint8 不佔字節) this
仍是上面的例子只是將spa
var xh Person = xitehip{age:18}
複製代碼
換成了debug
var xh Person = &xitehip{age:18}
複製代碼
那指針類型的變量是如何轉換成接口類型的呢? 見下方彙編代碼:
0x001d 00029 (main.go:13) PCDATA $2, $1
0x001d 00029 (main.go:13) PCDATA $0, $0
0x001d 00029 (main.go:13) LEAQ type."".xitehip(SB), AX
0x0024 00036 (main.go:13) PCDATA $2, $0
0x0024 00036 (main.go:13) MOVQ AX, (SP)
0x0028 00040 (main.go:13) CALL runtime.newobject(SB)
0x002d 00045 (main.go:13) PCDATA $2, $1
0x002d 00045 (main.go:13) MOVQ 8(SP), AX
0x0032 00050 (main.go:13) MOVB $18, (AX)
複製代碼
發現了這個函數:
runtime.newobject(SB)
複製代碼
去看一下具體實現:
// implementation of new builtin
// compiler (both frontend and SSA backend) knows the signature
// of this function
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
複製代碼
編譯器自動生成了iface並將&xitehip{age:18}建立的對象的地址(經過newobject)賦值給iface.data。就是xitehip這個結構體沒有被複制。 用gdb看一下見圖5:
把上面的例子添加一個eat()接口方法並實現它(注意這個接口方法的實現的接受者是指針)。
package main
type Person interface {
run()
eat(string)
}
type xitehip struct {
age uint8
}
func (o xitehip)run() { // //接收方o是值
}
func (o *xitehip)eat(food string) { //接收方o是指針
}
func main() {
var xh Person = &xitehip{age:18} //xh是指針
xh.eat("ma la xiao long xia!")
xh.run()
}
複製代碼
這個例子的xh變量的實際類型是個指針,那它是如何調用非指針方法run的呢? 繼續gdb跟蹤一下,見圖7:
那xh是值類型的接口,而接口實現的方法的接收方是指針類型,那調用方能夠調用這個指針方法嗎,答案是不只不能連編譯都編譯不過去,見圖11:
調用方 | 接收方 | 可否編譯 |
---|---|---|
值 | 值 | true |
值 | 指針 | false |
指針 | 值 | true |
指針 | 指針 | true |
指針 | 指針和值 | true |
值 | 指針和值 | false |
從上表能夠得出以下結論:
調用方是值時,只要接收方有指針方法那編譯器不容許經過編譯。
空接口相對於非空接口沒有了方法列表。
type eface struct {
_type *_type
data unsafe.Pointer
}
複製代碼
第一個屬性由itab換成了_type,這個結構體是golang中的變量類型的基礎,因此空接口能夠指定任意變量類型。
cpackage main
import "fmt"
type xitehip struct {
}
func main() {
var a interface{} = xitehip{}
var b interface{} = &xitehip{}
fmt.Println(a)
fmt.Println(b)
}
複製代碼
gdb跟一下見圖12:
判斷變量數據類型
s, ok := i.(TypeName)
if ok {
fmt.Println(s)
}
複製代碼
若是沒有ok的話類型不正確的話會引發panic。
也能夠用switch形式:
switch v := v.(type) {
case TypeName:
...
}
複製代碼
var _ InterfaceName = (*TypeName)(nil)
func main() {
var i interface{}
if i == nil {
println(「The interface is nil.「)
}
}
(gdb) info locals;
i = {_type = 0x0, data = 0x0}
複製代碼
// go:noinline
func main() {
var o *int = nil
var i interface{} = o
if i == nil {
println("Nil")
}
println(i)
}
(gdb) info locals;
i = {_type = 0x21432f8 <type.*+36723>, data = 0x0}
o = 0x0
複製代碼
v := reflect.ValueOf(a)
if v.Isvalid() {
println(v.IsNil()) // true, This is nil interface
}
複製代碼
參考