欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang中的Interface詳解

 更新時間:2022年07月13日 14:39:02   作者:qqwx  
本文詳細(xì)講解了Golang中的Interface,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

背景:

golang的interface是一種satisfied式的。A類只要實現(xiàn)了IA interface定義的方法,A就satisfied了接口IA。更抽象一層,如果某些設(shè)計上需要一些更抽象的共性,比如print各類型,這時需要使用reflect機(jī)制,reflect實質(zhì)上就是將interface的實現(xiàn)暴露了一部分給應(yīng)用代碼。要理解reflect,需要深入了解interface。go的interface是一種隱式的interface,但golang的類型是編譯階段定的,是static的,如:

type MyInt int
var i int
var j MyInt

雖然MyInt底層就是int,但在編譯器角度看,i的類型是int,j的類型是MyInt,是靜態(tài)、不一致的。兩者要賦值必須要進(jìn)行類型轉(zhuǎn)換。即使是interface,就語言角度來看也是靜態(tài)的。如:

var r io.Reader

不管r后面用什么來初始化,它的類型總是io.Reader。更進(jìn)一步,對于空的interface,也是如此。記住go語言類型是靜態(tài)這一點(diǎn),對于理解interface/reflect很重要。看一例:

var r io.Reader
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
    return nil, err
}
r = tty

到這里,r的類型是什么?r的類型仍然是interface io.Reader,只是r = tty這一句,隱含了一個類型轉(zhuǎn)換,將tty轉(zhuǎn)成了io.Reader。

interface的實現(xiàn):

作為一門編程語言,對方法的處理一般分為兩種類型:一是將所有方法組織在一個表格里,靜態(tài)地調(diào)用(C++, java);二是調(diào)用時動態(tài)查找方法(python, smalltalk, js)。而go語言是兩者的結(jié)合:雖然有table,但是是需要在運(yùn)行時計算的table。如下例:Binary類實現(xiàn)了兩個方法,String()和Get()

type Binary uint64
func (i Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}
  
func (i Binary) Get() uint64 {
    return uint64(i)
}

因為它實現(xiàn)了String(),按照golang的隱式方法實現(xiàn)來看,Binary satisfied了Stringer接口。因此它可以賦值: s:=Stringer(b)。以此為例來說明下interface的實現(xiàn):interface的內(nèi)存組織如圖:

一個interface值由兩個指針組成,第一個指向一個interface table,叫 itable。itable開頭是一些描述類型的元字段,后面是一串方法。注意這個方法是interface本身的方法,并非其dynamic value(Binary)的方法。即這里只有String()方法,而沒有Get方法。但這個方法的實現(xiàn)肯定是具體類的方法,這里就是Binary的方法。
當(dāng)這個interface無方法時,itable可以省略,直接指向一個type即可。
另一個指針data指向dynamic value的一個拷貝,這里則是b的一份拷貝。也就是,給interface賦值時,會在堆上分配內(nèi)存,用于存放拷貝的值。
同樣,當(dāng)值本身只有一個字長時,這個指針也可以省略。
一個interface的初始值是兩個nil。比如,

var w io.Writer

這時,tab和data都是nil。interface是否為nil取決于itable字段。所以不一定data為nil就是nil,判斷時要額外注意。

這樣,像這樣的代碼:

switch v := any.(type) {
case int:
    return strconv.Itoa(v)
case float:
    return strconv.Ftoa(v, 'g', -1)
}

實際上是any這個interface取了  any. tab->type。

而interface的函數(shù)調(diào)用實際上就變成了:

s.tab->fun[0](s.data)。第一個參數(shù)即自身類型指針。

itable的生成:

itable的生成是理解interface的關(guān)鍵。

如剛開始處提的,為了支持go語言這種接口間僅通過方法來聯(lián)系的特性,是沒有辦法像C++一樣,在編譯時預(yù)先生成一個method table的,只能在運(yùn)行時生成。因此,自然的,所有的實體類型都必須有一個包含此類型所有方法的“類型描述符”(type description structure);而interface類型也同樣有一個類似的描述符,包含了所有的方法。

這樣,interface賦值時,計算interface對象的itable時,需要對兩種類型的方法列表進(jìn)行遍歷對比。如后面代碼所示,這種計算只需要進(jìn)行一次,而且優(yōu)化成了O(m+n)。

可見,interface與itable之間的關(guān)系不是獨(dú)立的,而是與interface具體的value類型有關(guān)。即(interface類型, 具體類型)->itable。

var any interface{}  // initialized elsewhere
s := any.(Stringer)  // dynamic conversion
for i := 0; i < 100; i++ {
    fmt.Println(s.String())
}

itable的計算不需要到函數(shù)調(diào)用時進(jìn)行,只需要在interface賦值時進(jìn)行即可,如上第2行,不需要在第4行進(jìn)行。

最后,看一些實現(xiàn)代碼:

以下是上面圖中的兩個字段。

type iface struct {
    tab  *itab     // 指南itable
    data unsafe.Pointer     // 指向真實數(shù)據(jù)
}

再看itab的實現(xiàn):

type itab struct {
    inter  *interfacetype
    _type  *_type
    link   *itab
    bad    int32
    unused int32
    fun    [1]uintptr // variable sized
}

可見,它使用一個疑似鏈表的東西,可以猜這是用作hash表的拉鏈。前兩個字段應(yīng)該是用來表達(dá)具體的interface類型和實際擁有的值的類型的,即一個itable的key。(上文提到的(interface類型, 具體類型) )

type imethod struct {
    name nameOff
    ityp typeOff
}

type interfacetype struct {
    typ     _type
    pkgpath name
    mhdr    []imethod
}

interfacetype如有若干imethod,可以猜想這是表達(dá)interface定義的方法數(shù)據(jù)結(jié)構(gòu)。

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,可見里面有g(shù)c的東西,應(yīng)該就是具體的類型了。這里有個hash字段,itable實現(xiàn)就是掛在一個全局的hash table中。hash時用到了這個字段:

func itabhash(inter *interfacetype, typ *_type) uint32 {
    // compiler has provided some good hash codes for us.
    h := inter.typ.hash
    h += 17 * typ.hash
    // TODO(rsc): h += 23 * x.mhash ?
    return h % hashSize
}

可見,這里有個把interface類型與具體類型之間的信息結(jié)合起來做一個hash的過程,這個hash就是上述的itab的存儲地點(diǎn),itab中的link就是hash中的拉鏈。

回到itab,看取一個itab的邏輯:

如果發(fā)生了typeassert或是interface的賦值(強(qiáng)轉(zhuǎn)),需要臨時計算一個itab。這時會先在hash表中找,找不到才會真實計算。

     h := itabhash(inter, typ)

     // look twice - once without lock, once with.
     // common case will be no lock contention.
     var m *itab
     var locked int
     for locked = 0; locked < 2; locked++ {
         if locked != 0 {
             lock(&ifaceLock)
         }
         for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link {
             if m.inter == inter && m._type == typ {
                 return m    // 找到了前面計算過的itab
             }
         }
     }
    // 沒有找到,生成一個,并加入到itab的hash中。
     m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
     m.inter = inter
     m._type = typ
     additab(m, true, canfail)

這個hash是個全局變量:

 const (
     hashSize = 1009
 )

 var (
     ifaceLock mutex // lock for accessing hash
     hash      [hashSize]*itab
 )

最后,看一下如何生成itab:

     // both inter and typ have method sorted by name,
     // and interface names are unique,
     // so can iterate over both in lock step;
     // the loop is O(ni+nt) not O(ni*nt).       // 按name排序過的,因此這里的匹配只需要O(ni+nt)
     j := 0
     for k := 0; k < ni; k++ {
         i := &inter.mhdr[k]
         itype := inter.typ.typeOff(i.ityp)
         name := inter.typ.nameOff(i.name)
         iname := name.name()
         for ; j < nt; j++ {
             t := &xmhdr[j]
             tname := typ.nameOff(t.name)
             if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
                     if m != nil {
                         ifn := typ.textOff(t.ifn)
                         *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn // 找到匹配,將實際類型的方法填入itab的fun
                     }
                     goto nextimethod
                 }
             }
         }
     nextimethod:
     }
     h := itabhash(inter, typ)             //插入上面的全局hash
     m.link = hash[h]
     atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m))
 }

到這里,interface的數(shù)據(jù)結(jié)構(gòu)的框架。

reflection實質(zhì)上是將interface背后的實現(xiàn)暴露了一部分給應(yīng)用代碼,使應(yīng)用程序可以使用interface實現(xiàn)的一些內(nèi)容。只要理解了interface的實現(xiàn),reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value。

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • Go基礎(chǔ)教程系列之WaitGroup用法實例詳解

    Go基礎(chǔ)教程系列之WaitGroup用法實例詳解

    這篇文章主要介紹了Go基礎(chǔ)教程系列之WaitGroup用法實例詳解,需要的朋友可以參考下
    2022-04-04
  • Golang接口的定義與空接口及斷言的使用示例

    Golang接口的定義與空接口及斷言的使用示例

    在?Golang?中,接口是一種類型,它是由一組方法簽名組成的抽象集合。這篇文章主要為大家介紹了Golang接口的具體使用,希望對大家有所幫助,空接口是特殊形式的接口類型,普通的接口都有方法,而空接口沒有定義任何方法口,也因此,我們可以說所有類型都至少實現(xiàn)了空接口
    2023-04-04
  • Go語言原子操作及互斥鎖的區(qū)別

    Go語言原子操作及互斥鎖的區(qū)別

    原子操作就是不可中斷的操作,本文主要介紹了Go語言原子操作及互斥鎖的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • go代碼實現(xiàn)買房貸款月供計算的方法

    go代碼實現(xiàn)買房貸款月供計算的方法

    今天小編就為大家分享一篇關(guān)于go代碼實現(xiàn)買房貸款月供計算的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-04-04
  • 一文搞懂Golang?值傳遞還是引用傳遞

    一文搞懂Golang?值傳遞還是引用傳遞

    最多人犯迷糊的就是?slice、map、chan?等類型,都會認(rèn)為是?“引用傳遞”,從而認(rèn)為?Go?語言的?xxx?就是引用傳遞。正因為它們還引用類型(指針、map、slice、chan等這些),這樣就可以修改原內(nèi)容數(shù)據(jù),這篇文章主要介紹了Golang?值傳遞還是引用傳遞,需要的朋友可以參考下
    2023-01-01
  • golang中命令行庫cobra的使用方法示例

    golang中命令行庫cobra的使用方法示例

    這篇文章主要給大家介紹了關(guān)于golang中命令行庫cobra的使用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-08-08
  • golang簡易實現(xiàn)?k8s?的yaml上傳并應(yīng)用示例方案

    golang簡易實現(xiàn)?k8s?的yaml上傳并應(yīng)用示例方案

    這篇文章主要為大家介紹了golang簡易實現(xiàn)?k8s?的yaml上傳并應(yīng)用示例方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • 在Go中構(gòu)建并發(fā)TCP服務(wù)器

    在Go中構(gòu)建并發(fā)TCP服務(wù)器

    今天小編就為大家分享一篇關(guān)于在Go中構(gòu)建并發(fā)TCP服務(wù)器的文章,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • golang?gorm更新日志執(zhí)行SQL示例詳解

    golang?gorm更新日志執(zhí)行SQL示例詳解

    這篇文章主要為大家介紹了golang?gorm更新日志執(zhí)行SQL示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Go os/exec使用方式實踐

    Go os/exec使用方式實踐

    這篇文章主要介紹了Go os/exec使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-07-07

最新評論