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

深入Golang的接口interface

 更新時(shí)間:2022年06月08日 15:50:18   作者:? 談笑風(fēng)生間?  ?  
這篇文章主要介紹了深入Golang的接口interface,go不要求類型顯示地聲明實(shí)現(xiàn)了哪個(gè)接口,只要實(shí)現(xiàn)了相關(guān)的方法即可,編譯器就能檢測(cè)到,接下來(lái)關(guān)于接口interface的相關(guān)介紹需要的朋友可以參考下面文章內(nèi)容

前言

go不要求類型顯示地聲明實(shí)現(xiàn)了哪個(gè)接口,只要實(shí)現(xiàn)了相關(guān)的方法即可,編譯器就能檢測(cè)到

空接口類型可以接收任意類型的數(shù)據(jù):

type eface struct {
    // _type 指向接口的動(dòng)態(tài)類型元數(shù)據(jù)
    // 描述了實(shí)體類型、包括內(nèi)存對(duì)齊方式、大小等
	_type *_type
    // data 指向接口的動(dòng)態(tài)值
	data  unsafe.Pointer
}

空接口在賦值時(shí),_type 和 data 都是nil。賦值后,_type 會(huì)指向賦值的數(shù)據(jù)元類型,data 會(huì)指向該值

非空接口是有方法列表的接口類型,一個(gè)變量要賦值給非空接口,就要實(shí)現(xiàn)該接口里的所有方法

type iface struct {
    // tab 接口表指針,指向一個(gè)itab實(shí)體,存儲(chǔ)方法列表和接口動(dòng)態(tài)類型信息
	tab  *itab
    // data 指向接口的動(dòng)態(tài)值(一般是指向堆內(nèi)存的)
	data unsafe.Pointer
}
// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs.
type itab struct {
    // inter 指向interface的類型元數(shù)據(jù),描述了接口的類型
	inter *interfacetype
    // _type 描述了實(shí)體類型、包括內(nèi)存對(duì)齊方式、大小等
	_type *_type
    // hash 從動(dòng)態(tài)類型元數(shù)據(jù)中拷貝的hash值,用于快速判斷類型是否相等
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
    // fun 記錄動(dòng)態(tài)類型實(shí)現(xiàn)的那些接口方法的地址
    // 存儲(chǔ)的是第一個(gè)方法的函數(shù)指針
    // 這些方法是按照函數(shù)名稱的字典序進(jìn)行排列的
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}

// interfacetype 接口的類型元數(shù)據(jù)
type interfacetype struct {
	typ     _type
    // 記錄定義了接口的包名
	pkgpath name
    // mhdr 標(biāo)識(shí)接口所定義的函數(shù)列表
	mhdr    []imethod
}

itab是可復(fù)用的,go會(huì)將itab緩存起來(lái),構(gòu)造一個(gè)hash表用于查出和查詢緩存信息<接口類型, 動(dòng)態(tài)類型>

如果itab緩存中有,可以直接拿來(lái)使用,如果沒(méi)有,則新創(chuàng)建一個(gè)itab,并放入緩存中

一個(gè)Iface中的具體類型中實(shí)現(xiàn)的方法會(huì)被拷貝到Itab的fun數(shù)組中

// Note: change the formula in the mallocgc call in itabAdd if you change these fields.
type itabTableType struct {
	size    uintptr             // length of entries array. Always a power of 2.
	count   uintptr             // current number of filled entries.
	entries [itabInitSize]*itab // really [size] large
}
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
	// compiler has provided some good hash codes for us.
    // 用接口類型hash和動(dòng)態(tài)類型hash進(jìn)行異或
	return uintptr(inter.typ.hash ^ typ.hash)
}

接口類型和nil作比較

接口值的零值指接口動(dòng)態(tài)類型和動(dòng)態(tài)值都為nil,當(dāng)且僅當(dāng)此時(shí)接口值==nil

如何打印出接口的動(dòng)態(tài)類型和值?

定義一個(gè)iface結(jié)構(gòu)體,用兩個(gè)指針來(lái)描述itab和data,然后將具體遍歷在內(nèi)存中的內(nèi)容強(qiáng)行解釋為我們定義的iface

type iface struct{
    itab, data uintptr
}
func main() {
	var a interface{} = nil
	var b interface{} = (*int)(nil)
	x := 5
	var c interface{} = (*int)(&x)
	ia := *(*iface)(unsafe.Pointer(&a))
	ib := *(*iface)(unsafe.Pointer(&b))
	ic := *(*iface)(unsafe.Pointer(&c))
	fmt.Println(ia, ib, ic)
	fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}
// 輸出
// {0 0} {17426912 0} {17426912 842350714568}
// 5

檢測(cè)類型是否實(shí)現(xiàn)了接口:

賦值語(yǔ)句會(huì)發(fā)生隱式的類型轉(zhuǎn)換,在轉(zhuǎn)換過(guò)程中,編譯器會(huì)檢測(cè)等號(hào)右邊的類型是否實(shí)現(xiàn)了等號(hào)左邊接口所規(guī)定的函數(shù)

// 檢查 *myWriter 類型是否實(shí)現(xiàn)了 io.Writer 接口
var _ io.Writer = (*myWriter)(nil)
// 檢查 myWriter 類型是否實(shí)現(xiàn)了 io.Writer 接口
var _ io.Writer = myWriter{}

接口轉(zhuǎn)換的原理

將一個(gè)接口轉(zhuǎn)換為另一個(gè)接口:

  • 實(shí)際上是調(diào)用了convI2I
  • 如果目標(biāo)是空接口或和目標(biāo)接口的inter一樣就直接返回
  • 否則去獲取itab,然后將值data賦入,再返回
// 接口間的賦值實(shí)際上是調(diào)用了runtime.convI2I
// 實(shí)際上是要找到新interface的tab和data
// convI2I returns the new itab to be used for the destination value
// when converting a value with itab src to the dst interface.
func convI2I(dst *interfacetype, src *itab) *itab {
	if src == nil {
		return nil
	}
	if src.inter == dst {
		return src
	}
	return getitab(dst, src._type, false)
}

// 關(guān)鍵函數(shù),獲取itab
// getitab根據(jù)interfacetype和_type去全局的itab哈希表中查找,如果找到了直接返回
// 否則根據(jù)inter和typ新生成一個(gè)itab,插入到全局itab哈希表中
// 
// 查找了兩次,第二次上鎖了,目的是可能會(huì)寫入hash表,阻塞其余協(xié)程的第二次查找
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
	// 函數(shù)列表為空
    if len(inter.mhdr) == 0 {
		throw("internal error - misuse of itab")
	}

	// easy case
	if typ.tflag&tflagUncommon == 0 {
		if canfail {
			return nil
		}
		name := inter.typ.nameOff(inter.mhdr[0].name)
		panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
	}

	var m *itab

	// First, look in the existing table to see if we can find the itab we need.
	// This is by far the most common case, so do it without locks.
	// Use atomic to ensure we see any previous writes done by the thread
	// that updates the itabTable field (with atomic.Storep in itabAdd).
    // 使用原子性去保證我們能看見(jiàn)在該線程之前的任意寫操作
    // 確保更新全局hash表的字段
	t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
    // 遍歷一次,找到了就返回
	if m = t.find(inter, typ); m != nil {
		goto finish
	}
    // 沒(méi)找到就上鎖,再試一次
	// Not found.  Grab the lock and try again.
	lock(&itabLock)
	if m = itabTable.find(inter, typ); m != nil {
		unlock(&itabLock)
		goto finish
	}
    // hash表中沒(méi)找到itab就新生成一個(gè)itab
	// Entry doesn't exist yet. Make a new entry & add it.
	m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
	m.inter = inter
	m._type = typ
	// The hash is used in type switches. However, compiler statically generates itab's
	// for all interface/type pairs used in switches (which are added to itabTable
	// in itabsinit). The dynamically-generated itab's never participate in type switches,
	// and thus the hash is irrelevant.
	// Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
	m.hash = 0
	m.init()
    // 加到全局的hash表中
	itabAdd(m)
	unlock(&itabLock)
finish:
	if m.fun[0] != 0 {
		return m
	}
	if canfail {
		return nil
	}
	// this can only happen if the conversion
	// was already done once using the , ok form
	// and we have a cached negative result.
	// The cached result doesn't record which
	// interface function was missing, so initialize
	// the itab again to get the missing function name.
	panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

// 查找全局的hash表,有沒(méi)有itab
// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
	// Implemented using quadratic probing.
	// Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
	// We're guaranteed to hit all table entries using this probe sequence.
	mask := t.size - 1
    // 根據(jù)inter,typ算出hash值
	h := itabHashFunc(inter, typ) & mask
	for i := uintptr(1); ; i++ {
		p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
		// Use atomic read here so if we see m != nil, we also see
		// the initializations of the fields of m.
		// m := *p
		m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
		if m == nil {
			return nil
		}
        // inter和typ指針都相同
		if m.inter == inter && m._type == typ {
			return m
		}
		h += i
		h &= mask
	}
}
// 核心函數(shù)。填充itab
// 檢查_(kāi)type是否符合interface_type并且創(chuàng)建對(duì)應(yīng)的itab結(jié)構(gòu)體將其放到hash表中
// init fills in the m.fun array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.fun[0] to 0 and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {
	inter := m.inter
	typ := m._type
	x := typ.uncommon()

	// 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).
    // inter和typ的方法都按方法名稱進(jìn)行排序
    // 并且方法名是唯一的,因此循環(huán)次數(shù)的固定的
    // 復(fù)雜度為O(ni+nt),而不是O(ni*nt)
	ni := len(inter.mhdr)
	nt := int(x.mcount)
	xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
	j := 0
	methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]
	var fun0 unsafe.Pointer
imethods:
	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()
		ipkg := name.pkgPath()
		if ipkg == "" {
			ipkg = inter.pkgpath.name()
		}
        // 第二層循環(huán)是從上一次遍歷到的位置開(kāi)始的
		for ; j < nt; j++ {
			t := &xmhdr[j]
			tname := typ.nameOff(t.name)
            // 檢查方法名字是否一致
			if typ.typeOff(t.mtyp) == itype && tname.name() == iname {
				pkgPath := tname.pkgPath()
				if pkgPath == "" {
					pkgPath = typ.nameOff(x.pkgpath).name()
				}
				if tname.isExported() || pkgPath == ipkg {
					if m != nil {
                        // 獲取函數(shù)地址,放入itab.func數(shù)組中
						ifn := typ.textOff(t.ifn)
						if k == 0 {
							fun0 = ifn // we'll set m.fun[0] at the end
						} else {
							methods[k] = ifn
						}
					}
					continue imethods
				}
			}
		}
		// didn't find method
		m.fun[0] = 0
		return iname
	}
	m.fun[0] = uintptr(fun0)
	return ""
}

// 檢查是否需要擴(kuò)容,并調(diào)用方法將itab存入
// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
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
    // 檢查是否需要擴(kuò)容
	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)*goarch.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.
	}
    // 核心函數(shù)
	t.add(m)
}

// 核心函數(shù)
// add adds the given itab to itab table t.
// itabLock must be held.
func (t *itabTableType) add(m *itab) {
	// See comment in find about the probe sequence.
	// Insert new itab in the first empty spot in the probe sequence.
    // 在探針序列第一個(gè)空白點(diǎn)插入itab
	mask := t.size - 1
    // 計(jì)算hash值
	h := itabHashFunc(m.inter, m._type) & mask
	for i := uintptr(1); ; i++ {
		p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
		m2 := *p
		if m2 == m {
            // itab已被插入
			// A given itab may be used in more than one module
			// and thanks to the way global symbol resolution works, the
			// pointed-to itab may already have been inserted into the
			// global 'hash'.
			return
		}
		if m2 == nil {
			// Use atomic write here so if a reader sees m, it also
			// sees the correctly initialized fields of m.
			// NoWB is ok because m is not in heap memory.
			// *p = m
            // 使用原子操作確保其余的goroutine下次查找的時(shí)候可以看到他
			atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
			t.count++
			return
		}
		h += i
		h &= mask
	}
}

// hash函數(shù),編譯期間提供較好的hash codes
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
	// compiler has provided some good hash codes for us.
	return uintptr(inter.typ.hash ^ typ.hash)
}

具體類型轉(zhuǎn)空接口時(shí),_type 字段直接復(fù)制源類型的 _type;調(diào)用 mallocgc 獲得一塊新內(nèi)存,把值復(fù)制進(jìn)去,data 再指向這塊新內(nèi)存。

具體類型轉(zhuǎn)非空接口時(shí),入?yún)?tab 是編譯器在編譯階段預(yù)先生成好的,新接口 tab 字段直接指向入?yún)?tab 指向的 itab;調(diào)用 mallocgc 獲得一塊新內(nèi)存,把值復(fù)制進(jìn)去,data 再指向這塊新內(nèi)存。

而對(duì)于接口轉(zhuǎn)接口,itab 調(diào)用 getitab 函數(shù)獲取。只用生成一次,之后直接從 hash 表中獲取。

實(shí)現(xiàn)多態(tài)

  • 多態(tài)的特點(diǎn)
  • 一種類型具有多種類型的能力
  • 允許不同的對(duì)象對(duì)同一消息做出靈活的反應(yīng)
  • 以一種通用的方式對(duì)待多個(gè)使用的對(duì)象
  • 非動(dòng)態(tài)語(yǔ)言必須通過(guò)繼承和接口的方式實(shí)現(xiàn)

實(shí)現(xiàn)函數(shù)的內(nèi)部,接口綁定了實(shí)體類型,會(huì)直接調(diào)用fun里保存的函數(shù),類似于s.tab->fun[0],而fun數(shù)組中保存的是實(shí)體類型實(shí)現(xiàn)的函數(shù),當(dāng)函數(shù)傳入不同實(shí)體類型時(shí),實(shí)際上調(diào)用的是不同的函數(shù)實(shí)現(xiàn),從而實(shí)現(xiàn)了多態(tài)

到此這篇關(guān)于深入Golang的接口interface的文章就介紹到這了,更多相關(guān)Go 接口 interface內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用go自定義prometheus的exporter

    使用go自定義prometheus的exporter

    在prometheus中如果要監(jiān)控服務(wù)器和應(yīng)用的各種指標(biāo),需要用各種各樣的exporter服務(wù),這篇文章主要介紹了使用go自定義prometheus的exporter,需要的朋友可以參考下
    2023-03-03
  • go sync Once實(shí)現(xiàn)原理示例解析

    go sync Once實(shí)現(xiàn)原理示例解析

    這篇文章主要為大家介紹了go sync Once實(shí)現(xiàn)原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 再次探討go實(shí)現(xiàn)無(wú)限 buffer 的 channel方法

    再次探討go實(shí)現(xiàn)無(wú)限 buffer 的 channel方法

    我們知道go語(yǔ)言內(nèi)置的channel緩沖大小是有上限的,那么我們自己如何實(shí)現(xiàn)一個(gè)無(wú)限 buffer 的 channel呢?今天通過(guò)本文給大家分享go實(shí)現(xiàn)無(wú)限 buffer 的 channel方法,感興趣的朋友一起看看吧
    2021-06-06
  • 使用gopkg.in/yaml.v3?解析YAML數(shù)據(jù)詳解

    使用gopkg.in/yaml.v3?解析YAML數(shù)據(jù)詳解

    這篇文章主要為大家介紹了使用gopkg.in/yaml.v3?解析YAML數(shù)據(jù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Go語(yǔ)言實(shí)現(xiàn)操作MySQL的基礎(chǔ)知識(shí)總結(jié)

    Go語(yǔ)言實(shí)現(xiàn)操作MySQL的基礎(chǔ)知識(shí)總結(jié)

    這篇文章主要總結(jié)一下怎么使用Go語(yǔ)言操作MySql數(shù)據(jù)庫(kù),文中的示例代碼講解詳細(xì),需要的朋友可以參考以下內(nèi)容,希望對(duì)大家有所幫助
    2022-09-09
  • golang http連接復(fù)用方法

    golang http連接復(fù)用方法

    今天小編就為大家分享一篇golang http連接復(fù)用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-07-07
  • Golang切片刪除指定元素的三種方法對(duì)比

    Golang切片刪除指定元素的三種方法對(duì)比

    Go語(yǔ)言并沒(méi)有提供用于刪除元素的語(yǔ)法或接口,而是通過(guò)利用切片本身的特性來(lái)刪除元素—追加元素,這篇文章主要給大家介紹了關(guān)于Golang切片刪除指定元素的三種方法,需要的朋友可以參考下
    2022-06-06
  • Golang 端口復(fù)用測(cè)試的實(shí)現(xiàn)

    Golang 端口復(fù)用測(cè)試的實(shí)現(xiàn)

    這篇文章主要介紹了Golang 端口復(fù)用測(cè)試的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Golang編程并發(fā)工具庫(kù)MapReduce使用實(shí)踐

    Golang編程并發(fā)工具庫(kù)MapReduce使用實(shí)踐

    這篇文章主要為大家介紹了Golang并發(fā)工具庫(kù)MapReduce的使用實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • 解決老版本goland無(wú)法調(diào)試新版本go的問(wèn)題

    解決老版本goland無(wú)法調(diào)試新版本go的問(wèn)題

    這篇文章主要給大家介紹了如何解決老版本goland無(wú)法調(diào)試新版本go的問(wèn)題,文中通過(guò)代碼示例給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2023-11-11

最新評(píng)論