亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go reflect 反射原理示例詳解

 更新時(shí)間:2022年11月02日 10:48:37   作者:飛天薯?xiàng)l  
這篇文章主要為大家介紹了Go reflect 反射原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

開(kāi)始之前

在開(kāi)始分析原理之前,有必要問(wèn)一下自己一個(gè)問(wèn)題:

反射是什么?以及其作用是什么?

不論在哪種語(yǔ)言中,我們所提到的反射功能,均指開(kāi)發(fā)者可以在運(yùn)行時(shí)通過(guò)調(diào)用反射庫(kù)來(lái)獲取到來(lái)獲取到指定對(duì)象類型信息,通常類型信息中會(huì)包含對(duì)象的字段/方法等信息。并且,反射庫(kù)通常會(huì)提供方法的調(diào)用, 以及字段賦值等功能。

使用反射可以幫助我們避免寫(xiě)大量重復(fù)的代碼, 因此反射功能常見(jiàn)用于ORM框架, 以及序列化何反序列化框架,除此之外在Java中反射還被應(yīng)用到了AOP等功能中。

了解完反射的功能之后,我們?cè)僖暌粋€(gè)問(wèn)題:

假如你開(kāi)發(fā)了一種語(yǔ)言, 該如何為開(kāi)發(fā)者提供反射的功能?

首先,我們知道反射的核心的功能有:

  • 類型信息獲取
  • 對(duì)象字段訪問(wèn)/賦值
  • 方法調(diào)用

因此實(shí)際作為語(yǔ)言的開(kāi)發(fā)者(假設(shè)),我們要解決的問(wèn)題有:

  • 如何存儲(chǔ)并獲取到對(duì)象類型信息?
  • 如何定位到對(duì)象字段的內(nèi)存地址?

注: 只要知道了對(duì)象字段的內(nèi)存地址配合上類型信息,我們便可以實(shí)現(xiàn)賦值與訪問(wèn)的操作。

  • 如何定位到方法的內(nèi)存地址?

注:代碼在內(nèi)存中也是數(shù)據(jù),因此只需要定位到代碼所在的地址,便可解決方法調(diào)用的問(wèn)題

分析

從何處獲取類型信息

如果你熟悉Go的reflect(反射)庫(kù), 相信你或多或少的聽(tīng)過(guò)反射三原則, 即:

  • interface{}可以反射出反射對(duì)象
  • 從反射對(duì)象中可以獲取到interface{}
  • 要修改反射對(duì)象, 其值必須可設(shè)置

根據(jù)以上三原則不難看出interface{}是實(shí)現(xiàn)反射功能的基石, 那么這是為什么呢?

要回答這個(gè)問(wèn)題,我們了解interface{}的本質(zhì)是什么。

interface{}本質(zhì)上Go提供的一種數(shù)據(jù)類型, 與其他數(shù)據(jù)類型不同的是, interface{}會(huì)為我們提供變量的類型信息以及變量所在的內(nèi)存地址。

Runtime中使用結(jié)構(gòu)體來(lái)表示interface{}, 其結(jié)構(gòu)如下所示:

type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer  
}

該結(jié)構(gòu)體只有兩個(gè)字段, 分別是:

  • typ 變量的類型信息, 這一步驟在編譯步驟便可確定下來(lái)
  • word 指向變量數(shù)據(jù)的指針, 這一步驟在運(yùn)行時(shí)進(jìn)行確定

接下來(lái)我們通過(guò)反編譯下文的代碼, 來(lái)觀察當(dāng)把一個(gè)變量轉(zhuǎn)換成interface{}的時(shí)候都發(fā)生了什么:

package main
import "fmt"
func main() {
	s := 1024
	var a interface{} = &s
	fmt.Println(a)
}

執(zhí)行以下命令, 獲取匯編代碼

go tool compile -N -S .\main.go

以下代碼即為將字符串賦值給interface{}類型的變量a的對(duì)應(yīng)匯編代碼

0x0057 00087 (.\main.go:7)      MOVQ    "".&s+104(SP), AX
0x005c 00092 (.\main.go:7)      MOVQ    AX, ""..autotmp_9+88(SP)
0x0061 00097 (.\main.go:7)      LEAQ    type.*int(SB), CX
0x0068 00104 (.\main.go:7)      MOVQ    CX, "".a+144(SP)
0x0070 00112 (.\main.go:7)      MOVQ    AX, "".a+152(SP)

相信即便你不熟悉匯編,但至少也發(fā)現(xiàn)了, 以上代碼做了如下操作:

  • 獲取變量s的地址, 保存到AX寄存器, 并往a+144的地址寫(xiě)入數(shù)據(jù)
  • 獲取變量s的類型信息(type.*int),保存到CX寄存器, 并往a+152的地址寫(xiě)入數(shù)據(jù)

注:感興趣的讀者可以把取地址的操作去掉,再看看有什么不同

此外, 我們還可以通過(guò)指針數(shù)據(jù)類型轉(zhuǎn)換來(lái)獲取到interface{}中的數(shù)據(jù)來(lái)側(cè)面驗(yàn)證一下。

注: unsafe.Pointer 可以轉(zhuǎn)換成任意類型的指針

type EmptyInterface struct {
	typ  unsafe.Pointer
	word unsafe.Pointer
}
func getWordPtr(i interface{})  unsafe.Pointer {
	eface := *(*EmptyInterface)(unsafe.Pointer(&i))
	return eface.word
}
func Test_GetWordPtr(t *testing.T) {
	str := "Hello, KeSan"
	strPtr := &str
	//此處由編譯器做了類型轉(zhuǎn)換 *string -> interface{}
	wordPtr := getWordPtr(strPtr)
	t.Logf("String Ptr: %p",  strPtr)
	t.Logf("Word Ptr: %p", wordPtr)
}

輸入如下所示:

因此,不難推出reflect.TypeOf的實(shí)現(xiàn)實(shí)際上就是獲取interface{}type信息,并返回給開(kāi)發(fā)人員。其代碼如下所示:

func TypeOf(i interface{}) Type {
	eface := *(*emptyInterface)(unsafe.Pointer(&i))
	return toType(eface.typ)
}
// 將 *rtype 轉(zhuǎn)成接口類型的Type
func toType(t *rtype) Type {
	if t == nil {
		return nil
	}
	return t
}

再進(jìn)一步我們可以來(lái)看看類型信息中都包含了什么?

結(jié)構(gòu)體rtype描述了基礎(chǔ)的類型信息,其字段如下所示:

type rtype struct {
	size       uintptr
	ptrdata    uintptr // number of bytes in the type that can contain pointers
	hash       uint32  // hash of type; avoids computation in hash tables
	tflag      tflag   // extra type information flags
	align      uint8   // alignment of variable with this type
	fieldAlign uint8   // alignment of struct field with this type
	kind       uint8   // enumeration for C
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal     func(unsafe.Pointer, unsafe.Pointer) bool
	gcdata    *byte   // garbage collection data
	str       nameOff // string form
	ptrToThis typeOff // type for pointer to this type, may be zero
}

rtype結(jié)構(gòu)體包含了Golang中所有數(shù)據(jù)類型的基礎(chǔ)類型信息, 對(duì)于不同的數(shù)據(jù)類型其類型信息會(huì)有略微的差異。

// 結(jié)構(gòu)體的類型信息
type structType struct {
	rtype
	pkgPath name
	fields  []structField // sorted by offset
}
// channel 的類型信息
type chanType struct {
	rtype
	elem *rtype  // channel element type
	dir  uintptr // channel direction (ChanDir)
}

如何實(shí)現(xiàn)賦值操作?

賦值操作的本質(zhì)上是往對(duì)應(yīng)的內(nèi)存地址寫(xiě)入數(shù)據(jù), 因此我們有必要簡(jiǎn)單了解一下結(jié)構(gòu)體在內(nèi)存中的布局方式, 以一個(gè)最為簡(jiǎn)單坐標(biāo)的結(jié)構(gòu)體為例,其結(jié)構(gòu)體如下所示:

type Coordinate struct {
    X int64
    Y int64
    Z int64
}

其在內(nèi)存中的表現(xiàn)為一段大小為24字節(jié)的連續(xù)內(nèi)存,具體如下圖所示

因此,我們實(shí)際上要做的就是獲取到結(jié)構(gòu)體的首地址之后,根據(jù)各個(gè)字段相對(duì)首字段的偏移地址計(jì)算出其在內(nèi)存中地址。

實(shí)際上在Runtime提供的類型信息中,已經(jīng)包含了各個(gè)字段的偏移以及類型信息,我們可以具體的來(lái)看一下反射功能獲取字段Field的實(shí)現(xiàn)。

func (v Value) Field(i int) Value {
	if v.kind() != Struct {
		panic(&ValueError{"reflect.Value.Field", v.kind()})
	}
	// 獲取類型信息
	tt := (*structType)(unsafe.Pointer(v.typ))
	if uint(i) >= uint(len(tt.fields)) {
		panic("reflect: Field index out of range")
	}
	// 獲取字段信息
	field := &tt.fields[i]
	typ := field.typ
	// 繼承結(jié)構(gòu)體的部分flag信息
	fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind())
	if !field.name.isExported() {
		if field.embedded() {
			fl |= flagEmbedRO
		} else {
			fl |= flagStickyRO
		}
	}
	// 根據(jù)偏移地址計(jì) + 結(jié)構(gòu)體的首地址 計(jì)算出 字段在內(nèi)存中的地址, 并返回Value對(duì)象
	ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")
	return Value{typ, ptr, fl}
}

了解到如何獲取字段在內(nèi)存中的地址之后,我們?cè)賮?lái)看看賦值操作是如何實(shí)現(xiàn)。

如以下代碼SetInt所示, 本質(zhì)上還是一些指針的轉(zhuǎn)換以及解引用。

func (v Value) SetInt(x int64) {
	v.mustBeAssignable()
	switch k := v.kind(); k {
	default:
		panic(&ValueError{"reflect.Value.SetInt", v.kind()})
	case Int:
		*(*int)(v.ptr) = int(x)
	case Int8:
		*(*int8)(v.ptr) = int8(x)
	case Int16:
		*(*int16)(v.ptr) = int16(x)
	case Int32:
		*(*int32)(v.ptr) = int32(x)
	case Int64:
		*(*int64)(v.ptr) = x
	}
}

那么,肯定有同學(xué)會(huì)問(wèn),為啥你一直都在講結(jié)構(gòu)體啊,那字符串(string), 切片(slice), map呢?

實(shí)際上這些Go的內(nèi)建的數(shù)據(jù)類型,在Runtime中的表現(xiàn)形式也是結(jié)構(gòu)體, 我們可以在reflect包中找到如下定義:

// 切片頭
type SliceHeader struct {
	Data uintptr // 數(shù)組的指針地址
	Len  int     // 數(shù)組長(zhǎng)度
	Cap  int     // 數(shù)組容量
}
// 字符串頭
type StringHeader struct {
	Data uintptr // 字節(jié)數(shù)組的指針地址
	Len  int     // 字節(jié)數(shù)組的長(zhǎng)度
}

因此,通過(guò)反射來(lái)操作切片和字符串本質(zhì)上還是操作結(jié)構(gòu)體。

總結(jié)

  • interface{}是一種數(shù)據(jù)類型, 其存儲(chǔ)了變量的類型信息與數(shù)據(jù)指針,其中類型信息是在編譯期間確定下來(lái)的
  • Golang反射的原理就是從interface{}中獲取到類型信息以及變量的指針,從而實(shí)現(xiàn)類型獲取以及賦值的功能

以上就是Go reflect 反射原理示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go reflect 反射原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語(yǔ)言中三種不同md5計(jì)算方式的性能比較

    Go語(yǔ)言中三種不同md5計(jì)算方式的性能比較

    md5計(jì)算在我們?nèi)粘9ぷ鞯臅r(shí)候經(jīng)常能遇到,下面這篇文章主要介紹了Go語(yǔ)言中三種不同md5計(jì)算方式的性能比較,需要的朋友可以參考借鑒,下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-01-01
  • golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能示例

    golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能示例

    這篇文章主要介紹了golang實(shí)現(xiàn)redis的延時(shí)消息隊(duì)列功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Go語(yǔ)言應(yīng)該什么情況使用指針

    Go語(yǔ)言應(yīng)該什么情況使用指針

    go語(yǔ)言的指針類型和C/C++的指針類型用法是一樣的,那么Go語(yǔ)言應(yīng)該什么情況使用指針,本文就詳細(xì)的介紹一下,感興趣的可以了解一下
    2021-07-07
  • 使用go自定義prometheus的exporter

    使用go自定義prometheus的exporter

    在prometheus中如果要監(jiān)控服務(wù)器和應(yīng)用的各種指標(biāo),需要用各種各樣的exporter服務(wù),這篇文章主要介紹了使用go自定義prometheus的exporter,需要的朋友可以參考下
    2023-03-03
  • 詳解Golang中下劃線的使用方法

    詳解Golang中下劃線的使用方法

    這篇文章主要介紹了詳解Golang中下劃線的使用方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • 手把手教你用VS?code快速搭建一個(gè)Golang項(xiàng)目

    手把手教你用VS?code快速搭建一個(gè)Golang項(xiàng)目

    Go語(yǔ)言是采用UTF8編碼的,理論上使用任何文本編輯器都能做Go語(yǔ)言開(kāi)發(fā),下面這篇文章主要給大家介紹了關(guān)于使用VS?code快速搭建一個(gè)Golang項(xiàng)目的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-04-04
  • golang為什么要統(tǒng)一錯(cuò)誤處理

    golang為什么要統(tǒng)一錯(cuò)誤處理

    這篇文章主要介紹了golang為什么要統(tǒng)一錯(cuò)誤處理,統(tǒng)一錯(cuò)誤處理的目的是為了前端開(kāi)發(fā)接收到后端的statuscode,之后便于前端邏輯上開(kāi)發(fā)以及開(kāi)發(fā),下文具體操作過(guò)程需要的小伙伴可以參考一下
    2022-04-04
  • sublime text3解決Gosublime無(wú)法自動(dòng)補(bǔ)全代碼的問(wèn)題

    sublime text3解決Gosublime無(wú)法自動(dòng)補(bǔ)全代碼的問(wèn)題

    本文主要介紹了sublime text3解決Gosublime無(wú)法自動(dòng)補(bǔ)全代碼的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Go語(yǔ)言CSP并發(fā)模型實(shí)現(xiàn)MPG

    Go語(yǔ)言CSP并發(fā)模型實(shí)現(xiàn)MPG

    這篇文章主要為大家介紹了Go語(yǔ)言CSP并發(fā)模型實(shí)現(xiàn)MPG圖文詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 深入解析Golang中JSON的編碼與解碼

    深入解析Golang中JSON的編碼與解碼

    隨著互聯(lián)網(wǎng)的快速發(fā)展和數(shù)據(jù)交換的廣泛應(yīng)用,各種數(shù)據(jù)格式的處理成為軟件開(kāi)發(fā)中的關(guān)鍵問(wèn)題,本文將介紹?Golang?中?JSON?編碼與解碼的相關(guān)知識(shí),幫助大家了解其基本原理和高效應(yīng)用,需要的可以收藏一下
    2023-05-05

最新評(píng)論