日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

 

1. 引言

反射是現代編程語言中非常重要的一個特性,尤其是在面向對象編程語言中

此前的文章中,我們看到 golang 如何實現面向對象的封裝: 通過 GoLang 實現面向對象思想

如果能夠熟練運用反射特性,可以在很多情況下寫出通用性極強的代碼,達到事半功倍的效果,那么,究竟什么是反射,在 golang 中反射又是如何實現的,本文我們就來詳細解讀。

2. 反射

什么是反射?為什么要使用反射?這是本文開始前必須要解決的兩個問題。

2.1. 什么是反射

反射機制是現代編程語言中一個比較高級的特性。 在編譯時不知道類型的情況下,通過反射機制可以獲取對象的類型、值、方法甚至動態改變對象的成員,這就是反射機制。

2.2. 為什么使用反射

在很多時候,我們都希望代碼具備良好的通用性。 最基本的,對于 fmt.Print 函數,他可以接受任意類型的對象作為參數,我們無法羅列出究竟有多少種入參的可能,此時,主動獲取入參的類型就可以大大簡化代碼的編寫。 更進一步,對于依賴注入與面向切面等設計模式來說,我們需要為被注入對象動態添加成員,動態調用對象的不同成員。 顯然,反射的存在極大地增加了代碼的靈活度與通用性。

3. golang 與反射

之前的文章中,我們講了 golang 的接口: golang 中的接口

golang 的接口作為 golang 語言中運行時類型抽象的主要工具,它的實現與反射機制的實現有著非常密切的關聯,他們都涉及 golang 中是如何管理類型的。 golang 中有兩種類型:

  1. static type — 靜態類型,創建變量時指定的類型,如 var str string,str 的類型 string 就是他的靜態類型
  2. concrete type — 運行時類型,如一個變量實現了接口中全部方法,那么這個變量的 concrete type 就是該接口類型

所以,golang 中,反射是必須與接口類型結合使用的。

4. golang 中的接口

golang 中的接口使用下面的幾個結構實現的:

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

// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
    // see ../runtime/iface.go:/Itab
    itab *struct {
        ityp *rtype // static interface type
        typ  *rtype // dynamic concrete type
        hash uint32 // copy of typ.hash
        _    [4]byte
        fun  [100000]unsafe.Pointer // method table
    }
    word unsafe.Pointer
}

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
    alg        *typeAlg // algorithm table
    gcdata     *byte    // garbage collection data
    str        nameOff  // string form
    ptrToThis  typeOff  // type for pointer to this type, may be zero
}

// An InterfaceType node represents an interface type.
InterfaceType struct {
    Interface  token.Pos  // position of "interface" keyword
    Methods    *FieldList // list of methods
    Incomplete bool       // true if (source) methods are missing in the Methods list
}

復制

因為 golang 中指針類型與指向區域的數據類型必須一致且不能變更,這為抽象功能的實現帶來了太大的局限,于是 golang 中提供了 unsafe 包,提供了對指針的增強功能,unsafe.Pointer類似于C中的void*,任何類型的指針都可以轉換為unsafe.Pointer 類型,unsafe.Pointer 類型也可以轉換為任何指針類型。 從上面的代碼中,我們看到,在 golang 中,不具有方法的接口類型與具有方法的接口類型是分別通過 eface 與 iface 兩種類型實現的。

 

eface 與 iface 兩者都同樣是由兩個指針來實現的,分別指向接口本身的類型描述結構與接口實現的內存空間。

4.1. 接口類型斷言的實現

此前介紹接口的文章中,我們有介紹到接口的類型斷言,其實現原理就是通過將斷言類型的 _type 與 data 指針指向的數據空間中的 type 進行比較實現的。 因此,即使斷言類型與數據類型在內存中是一模一樣的,也無法通過斷言實現其類型的轉換:

package main

import "fmt"

type temprature int

func main() {
    var temprature interface{} = temprature(5)
    fmt.Printf("temprature is %d", temprature.(int))
}

復制

雖然在內存中,我們定義的 temprature 與 int 是相同的,但其 _type 值是不同的,因此上述代碼拋出了 panic:

panic: interface conversion: interface {} is main.temprature, not int

5. 反射的實現 — reflect 包

在 golang 中,reflect 包實現了反射機制,它定義了兩個重要的類型:Type 和 Value,分別用來獲取接口類型變量的實際類型與值。 獲取他們的方法就是 TypeOf 和 ValueOf 方法。 我們修改一下上面的示例代碼:

package main

import (
    "fmt"
    "reflect"
)

type temprature int

func main() {
    var temp interface{} = temprature(5)
    fmt.Printf("temprature is %dn", temp.(temprature))
    itype := reflect.TypeOf(temp)
    ivalue := reflect.ValueOf(temp)
    fmt.Printf("%v: %v", itype, ivalue)
}

復制

打印出了:

temprature is 5 main.temprature: 5

可以看到,通過 TypeOf 與 ValueOf 方法,我們已經取到了接口變量的類型和實際的值。

6. golang 反射的實現原理

讓我們來看一下 TypeOf 與 ValueOf 的實現。

6.1. TypeOf 源碼

func TypeOf(i interface{}) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

func toType(t *rtype) Type {
    if t == nil {
        return nil
    }
    return t
}

復制

TypeOf 的實現通過 unsafe.Pointer 進行指針類型的強制轉換,從而通過返回的實例中獲取到內存中數據的實際類型字段。

6.2. ValueOf 源碼

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    escapes(i)

    return unpackEface(i)
}

func escapes(x interface{}) {
    if dummy.b {
        dummy.x = x
    }
}

func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    // NOTE: don't read e.word until we know whether it is really a pointer or not.
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

復制

ValueOf 的實現相較于 TypeOf 的實現顯得略為復雜一些。 在 unpackEface 函數中,同樣通過 unsafe.Pointer 將傳入參數轉換為了 emptyInterface 類型,從而可以獲取到傳入參數的類型字段與指向實際數據的指針,最終封裝為 Value 類型值返回:

type flag uintptr

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

復制

這個結構正是 golang 保存任何一個類型變量的存儲結構,因此,ValueOf 的返回值可以直接作為變量值來使用。

7. 后記

那么,在實際的使用場景中,反射能夠為我們帶來哪些便捷的功能呢?敬請期待下一篇文章 — golang 中反射的使用。

分享到:
標簽:golang
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定