Go語言反射reflect

目錄

反射是指在程序運(yùn)行期對(duì)程序本身進(jìn)行訪問和修改的能力唁桩。程序在編譯時(shí)瘫想,變量被轉(zhuǎn)換為內(nèi)存地址,變量名不會(huì)被編譯器寫入到可執(zhí)行部分瓮钥。在運(yùn)行程序時(shí)探遵,程序無法獲取自身的信息。

支持反射的語言可以在程序編譯期將變量的反射信息,如字段名稱囱晴、類型信息、結(jié)構(gòu)體信息等整合到可執(zhí)行文件中瓢谢,并給程序提供接口訪問反射信息畸写,這樣就可以在程序運(yùn)行期獲取類型的反射信息,并且有能力修改它們氓扛。

Go程序在運(yùn)行期使用reflect包訪問程序的反射信息枯芬。

reflect包實(shí)現(xiàn)了運(yùn)行時(shí)反射,允許程序操作任意類型的對(duì)象幢尚。典型用法是用靜態(tài)類型interface{}保存一個(gè)值破停,通過調(diào)用TypeOf獲取其動(dòng)態(tài)類型信息,該函數(shù)返回一個(gè)Type類型值尉剩。調(diào)用ValueOf函數(shù)返回一個(gè)Value類型值真慢,該值代表運(yùn)行時(shí)的數(shù)據(jù)。Zero接受一個(gè)Type類型參數(shù)并返回一個(gè)代表該類型零值的Value類型值理茎。

Go 程序的反射系統(tǒng)無法獲取到一個(gè)可執(zhí)行文件空間中或者是一個(gè)包中的所有類型信息黑界,需要配合使用標(biāo)準(zhǔn)庫中對(duì)應(yīng)的詞法、語法解析器和抽象語法樹(AST)對(duì)源碼進(jìn)行掃描后獲得這些信息皂林。

通過反射獲取類型信息

先上圖朗鸠,有了這張圖再看代碼的時(shí)候就會(huì)感覺很好理解了。

image

通過反射獲取類型信息:(reflect.TypeOf()和reflect.Type)

使用 reflect.TypeOf() 函數(shù)可以獲得任意值的類型對(duì)象(reflect.Type)础倍,程序通過類型對(duì)象可以訪問任意值的類型信息烛占。下面通過例子來理解獲取類型對(duì)象的過程:

package main

import (
    "fmt"
    "reflect"
)

type Student struct {

    Name string
    Age  int
}

func main() {

    var stu Student

    typeOfStu := reflect.TypeOf(stu)

    fmt.Println(typeOfStu.Name(), typeOfStu.Kind())
}

代碼輸出如下:

Student struct

代碼說明如下:

  • 第16行,定義一個(gè)int類型的變量
  • 第18行,通過reflect.TypeOf()取得變量stu的類型對(duì)象typeOfStu,類型為reflect.Type
  • 第20行中忆家,通過typeOfStu類型對(duì)象的成員函數(shù)犹菇,可以分別獲取到 typeOfStu 變量的類型名為 Student,種類(Kind)為 struct芽卿。

理解反射的類型(Type)與種類(Kind)

在使用反射時(shí)揭芍,需要首先理解類型(Type)和種類(Kind)的區(qū)別。編程中卸例,使用最多的是類型称杨,但在反射中,當(dāng)需要區(qū)分一個(gè)大品種的類型時(shí)筷转,就會(huì)用到種類(Kind)姑原。例如,需要統(tǒng)一判斷類型中的指針時(shí)旦装,使用種類(Kind)信息就較為方便页衙。

反射種類(Kind)的定義

Go 程序中的類型(Type)指的是系統(tǒng)原生數(shù)據(jù)類型,如 int阴绢、string店乐、bool、float32 等類型呻袭,以及使用 type 關(guān)鍵字定義的類型眨八,這些類型的名稱就是其類型本身的名稱。例如使用 type A struct{} 定義結(jié)構(gòu)體時(shí)左电,A 就是 struct{} 的類型廉侧。

種類(Kind)指的是對(duì)象歸屬的品種,在 reflect 包中有如下定義:

type Kind uint

const (
    Invalid Kind = iota  // 非法類型
    Bool                                 // 布爾型
    Int                                // 有符號(hào)整型
    Int8                                 // 有符號(hào)8位整型
    Int16                                // 有符號(hào)16位整型
    Int32                                // 有符號(hào)32位整型
    Int64                                // 有符號(hào)64位整型
    Uint                                 // 無符號(hào)整型
    Uint8                                // 無符號(hào)8位整型
    Uint16                           // 無符號(hào)16位整型
    Uint32                           // 無符號(hào)32位整型
    Uint64                           // 無符號(hào)64位整型
    Uintptr                          // 指針
    Float32                          // 單精度浮點(diǎn)數(shù)
    Float64                          // 雙精度浮點(diǎn)數(shù)
    Complex64                        // 32位復(fù)數(shù)類型
    Complex128                   // 64位復(fù)數(shù)類型
    Array                                // 數(shù)組
    Chan                                 // 通道
    Func                                 // 函數(shù)
    Interface                        // 接口
    Map                                // 映射
    Ptr                                // 指針
    Slice                                // 切片
    String                           // 字符串
    Struct                           // 結(jié)構(gòu)體
    UnsafePointer                // 底層指針
)

Map篓足、Slice段誊、Chan 屬于引用類型,使用起來類似于指針栈拖,但是在種類常量定義中仍然屬于獨(dú)立的種類连舍,不屬于 Ptr。

type A struct{} 定義的結(jié)構(gòu)體屬于 Struct 種類涩哟,*A 屬于 Ptr索赏。

從類型對(duì)象中獲取類型名稱和種類的例子

Go 語言中的類型名稱對(duì)應(yīng)的反射獲取方法是 reflect.Type 中的 Name() 方法,返回表示類型名稱的字符串贴彼。

類型歸屬的種類(Kind)使用的是 reflect.Type 中的 Kind() 方法潜腻,返回 reflect.Kind 類型的常量。

下面的代碼中會(huì)對(duì)常量和結(jié)構(gòu)體進(jìn)行類型信息獲取器仗。

package main

import (
    "fmt"
    "reflect"
)
//定義一個(gè)Enum類型
type Enum int
const (
    Zero Enum = 0
)
type Student struct {

    Name string
    Age  int
}

func main() {

    //定義一個(gè)Student類型的變量
    var stu Student

    //獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
    typeOfStu := reflect.TypeOf(stu)

    //顯示反射類型對(duì)象的名稱和種類
    fmt.Println(typeOfStu.Name(), typeOfStu.Kind())

    //獲取Zero常量的反射類型對(duì)象
    typeOfZero := reflect.TypeOf(Zero)

    //顯示反射類型對(duì)象的名稱和種類
    fmt.Println(typeOfZero.Name(), typeOfZero.Kind())
}

代碼輸出如下:

Student struct
Enum int

代碼說明如下:

  • 第21行融涣,將 Student 實(shí)例化,并且使用 reflect.TypeOf() 獲取被實(shí)例化后的 Student 的反射類型對(duì)象。
  • 第27行暴心,輸出Student的類型名稱和種類妓盲,類型名稱就是 Student,而 Student 屬于一種結(jié)構(gòu)體種類专普,因此種類為 struct。
  • 第30行弹沽,Zero 是一個(gè) Enum 類型的常量檀夹。這個(gè) Enum 類型在第 9 行聲明,第 12 行聲明了常量策橘。如沒有常量也不能創(chuàng)建實(shí)例炸渡,通過 reflect.TypeOf() 直接獲取反射類型對(duì)象。
  • 第33行丽已,輸出 Zero 對(duì)應(yīng)的類型對(duì)象的類型名和種類蚌堵。

reflect.Elem() - 通過反射獲取指針指向的元素類型

通過反射獲取指針指向的元素類型:reflect.Elem()

Go 程序中對(duì)指針獲取反射對(duì)象時(shí),可以通過 reflect.Elem() 方法獲取這個(gè)指針指向的元素類型沛婴。這個(gè)獲取過程被稱為取元素吼畏,等效于對(duì)指針類型變量做了一個(gè)*操作,代碼如下:

package main

import (
    "fmt"
    "reflect"
)

type Student struct {

    Name string
    Age  int
}

func main() {

    //定義一個(gè)Student類型的指針變量
    var stu = &Student{Name:"kitty", Age: 20}

    //獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
    typeOfStu := reflect.TypeOf(stu)

    //顯示反射類型對(duì)象的名稱和種類
    fmt.Printf("name: '%v', kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())

    //取類型的元素
    typeOfStu = typeOfStu.Elem()

    //顯示反射類型對(duì)象的名稱和種類
    fmt.Printf("element name: '%v', element kind: '%v'\n", typeOfStu.Name(), typeOfStu.Kind())
}

代碼輸出如下:

name: '', kind: 'ptr'
element name: 'Student', element kind: 'struct'

代碼說明如下:

  • 第17行嘁灯,創(chuàng)建了一個(gè)Student結(jié)構(gòu)體的實(shí)例泻蚊,stu是一個(gè)*Student的指針變量
  • 第20行,對(duì)指針變量獲取反射類型信息丑婿。
  • 第23行性雄,輸出指針變量的類型名稱和種類。Go語言的反射中對(duì)所有指針變量的種類都是 Ptr羹奉,但注意秒旋,指針變量的類型名稱是空,不是 *Student诀拭。
  • 第26行迁筛,取指針類型的元素類型,也就是 Student 類型炫加。這個(gè)操作不可逆瑰煎,不可以通過一個(gè)非指針類型獲取它的指針類型。
  • 第29行俗孝,輸出指針變量指向元素的類型名稱和種類酒甸,得到了 Student 的類型名稱(Student)和種類(struct)。

通過反射獲取結(jié)構(gòu)體的成員類型

任意值通過 reflect.TypeOf() 獲得反射對(duì)象信息后赋铝,如果它的類型是結(jié)構(gòu)體插勤,可以通過反射值對(duì)象(reflect.Type)的 NumField() 和 Field() 方法獲得結(jié)構(gòu)體成員的詳細(xì)信息。與成員獲取相關(guān)的 reflect.Type 的方法如下表所示。

<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">結(jié)構(gòu)體成員訪問的方法列表</center>

方法 說明
Field(i int) StructField 根據(jù)索引农尖,返回索引對(duì)應(yīng)的結(jié)構(gòu)體字段的信息析恋。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
NumField() int 返回結(jié)構(gòu)體成員字段數(shù)量。當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
FieldByName(name string) (StructField, bool) 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段的信息盛卡。沒有找到時(shí) bool 返回 false助隧,當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
FieldByIndex(index []int) StructField 多層成員訪問時(shí),根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引滑沧,返回字段的信息并村。沒有找到時(shí)返回零值。當(dāng)類型不是結(jié)構(gòu)體或索引超界時(shí) 發(fā)生宕機(jī)
FieldByNameFunc( match func(string) bool) (StructField,bool) 根據(jù)匹配函數(shù)匹配需要的字段滓技。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)

結(jié)構(gòu)體字段類型

Type 的 Field() 方法返回 StructField 結(jié)構(gòu)哩牍,這個(gè)結(jié)構(gòu)描述結(jié)構(gòu)體的成員信息,通過這個(gè)信息可以獲取成員與結(jié)構(gòu)體的關(guān)系令漂,如偏移膝昆、索引、是否為匿名字段叠必、結(jié)構(gòu)體標(biāo)簽(Struct Tag)等荚孵,而且還可以通過 StructField 的 Type 字段進(jìn)一步獲取結(jié)構(gòu)體成員的類型信息。StructField 的結(jié)構(gòu)如下:

type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路徑
    Type      Type       // 字段反射類型對(duì)象
    Tag       StructTag  // 字段的結(jié)構(gòu)體標(biāo)簽
    Offset    uintptr    // 字段在結(jié)構(gòu)體中的相對(duì)偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否為匿名字段
}

字段說明如下挠唆。

  • Name:為字段名稱处窥。
  • PkgPath:字段在結(jié)構(gòu)體中的路徑。
  • Type:字段本身的反射類型對(duì)象玄组,類型為 reflect.Type滔驾,可以進(jìn)一步獲取字段的類型信息。
  • Tag:結(jié)構(gòu)體標(biāo)簽俄讹,為結(jié)構(gòu)體字段標(biāo)簽的額外信息哆致,可以單獨(dú)提取。
  • Index:FieldByIndex 中的索引順序患膛。
  • Anonymous:表示該字段是否為匿名字段摊阀。

獲取成員反射信息

下面代碼中,實(shí)例化一個(gè)結(jié)構(gòu)體并遍歷其結(jié)構(gòu)體成員踪蹬,再通過 reflect.Type 的 FieldByName() 方法查找結(jié)構(gòu)體中指定名稱的字段胞此,直接獲取其類型信息。

反射訪問結(jié)構(gòu)體成員類型及信息:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    // 聲明一個(gè)空結(jié)構(gòu)體
    type cat struct {
        Name string

        // 帶有結(jié)構(gòu)體tag的字段
        Type int `json:"type" id:"100"`
    }

    // 創(chuàng)建cat的實(shí)例
    ins := cat{Name: "mimi", Type: 1}

    // 獲取結(jié)構(gòu)體實(shí)例的反射類型對(duì)象
    typeOfCat := reflect.TypeOf(ins)

    // 遍歷結(jié)構(gòu)體所有成員
    for i := 0; i < typeOfCat.NumField(); i++ {

        // 獲取每個(gè)成員的結(jié)構(gòu)體字段類型
        fieldType := typeOfCat.Field(i)

        // 輸出成員名和tag
        fmt.Printf("name: %v  tag: '%v'\n", fieldType.Name, fieldType.Tag)
    }

    // 通過字段名, 找到字段類型信息
    if catType, ok := typeOfCat.FieldByName("Type"); ok {

        // 從tag中取出需要的tag
        fmt.Println(catType.Tag.Get("json"), catType.Tag.Get("id"))
    }
}

代碼輸出如下:

name: Name  tag: ''
name: Type  tag: 'json:"type" id:"100"'
type 100

代碼說明如下:

  • 第 11 行跃捣,聲明了帶有兩個(gè)成員的 cat 結(jié)構(gòu)體漱牵。
  • 第 15 行,Type 是 cat 的一個(gè)成員疚漆,這個(gè)成員類型后面帶有一個(gè)以```開始和結(jié)尾的字符串酣胀。這個(gè)字符串在 Go語言中被稱為 Tag(標(biāo)簽)刁赦。一般用于給字段添加自定義信息,方便其他模塊根據(jù)信息進(jìn)行不同功能的處理闻镶。
  • 第 19 行甚脉,創(chuàng)建 cat 實(shí)例,并對(duì)兩個(gè)字段賦值铆农。結(jié)構(gòu)體標(biāo)簽屬于類型信息牺氨,無須且不能賦值。
  • 第 22 行墩剖,獲取實(shí)例的反射類型對(duì)象波闹。
  • 第 25 行,使用 reflect.Type 類型的 NumField() 方法獲得一個(gè)結(jié)構(gòu)體類型共有多少個(gè)字段涛碑。如果類型不是結(jié)構(gòu)體,將會(huì)觸發(fā)宕機(jī)錯(cuò)誤孵淘。
  • 第 28 行蒲障,reflect.Type 中的 Field() 方法和 NumField 一般都是配對(duì)使用,用來實(shí)現(xiàn)結(jié)構(gòu)體成員的遍歷操作瘫证。
  • 第 31 行揉阎,使用 reflect.Type 的 Field() 方法返回的結(jié)構(gòu)不再是 reflect.Type 而是StructField 結(jié)構(gòu)體。
  • 第 35 行背捌,使用 reflect.Type 的 FieldByName() 根據(jù)字段名查找結(jié)構(gòu)體字段信息毙籽,cat Type 表示返回的結(jié)構(gòu)體字段信息,類型為 StructField毡庆,ok 表示是否找到結(jié)構(gòu)體字段的信息坑赡。
  • 第 38 行中,使用 StructField 中 Tag 的 Get() 方法么抗,根據(jù) Tag 中的名字進(jìn)行信息獲取毅否。

通過反射獲取值信息

反射不僅可以獲取值的類型信息,還可以動(dòng)態(tài)地獲取或者設(shè)置變量的值蝇刀。Go語言中使用 reflect.Value 獲取和設(shè)置變量的值螟加。

變量、interface{}和reflect.Value是可以相互轉(zhuǎn)換的吞琐。這點(diǎn)在實(shí)際開發(fā)中捆探,會(huì)經(jīng)常碰到。

image

使用反射值對(duì)象包裝任意值

Go 語言中站粟,使用 reflect.ValueOf() 函數(shù)獲得值的反射值對(duì)象(reflect.Value)黍图。書寫格式如下:

rValue := reflect.ValueOf(rawValue)

reflect.ValueOf 返回 reflect.Value 類型,包含有 rawValue 的值信息卒蘸。reflect.Value 與原值間可以通過值包裝和值獲取互相轉(zhuǎn)化雌隅。reflect.Value 是一些反射操作的重要類型翻默,如反射調(diào)用函數(shù)。

從反射值對(duì)象獲取被包裝的值

Go 語言中可以通過 reflect.Value 重新獲得原始值恰起。

從反射值對(duì)象(reflect.Value)中獲取值得方法

可以通過下面幾種方法從反射值對(duì)象 reflect.Value 中獲取原值修械,如下表所示蚊逢。

<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值獲取原始值的方法</center>

方法名 說 明
Interface() interface{} 將值以 interface{} 類型返回签则,可以通過類型斷言轉(zhuǎn)換為指定類型
Int() int64 將值以 int 類型返回,所有有符號(hào)整型均可以此方式返回
Uint() uint64 將值以 uint 類型返回彩届,所有無符號(hào)整型均可以此方式返回
Float() float64 將值以雙精度(float64)類型返回吨枉,所有浮點(diǎn)數(shù)(float32蹦渣、float64)均可以此方式返回
Bool() bool 將值以 bool 類型返回
Bytes() []bytes 將值以字節(jié)數(shù)組 []bytes 類型返回
String() string 將值以字符串類型返回

從反射值對(duì)象(reflect.Value)中獲取值得例子

下面代碼中,將整型變量中的值使用 reflect.Value 獲取反射值對(duì)象(reflect.Value)貌亭。再通過 reflect.Value 的 Interface() 方法獲得 interface{} 類型的原值柬唯,通過 int 類型對(duì)應(yīng)的 reflect.Value 的 Int() 方法獲得整型值。

package main

import (
    "fmt"
    "reflect"
)

func main() {

    //聲明整型變量a并賦初值
    var a int = 1024

    //獲取變量a的反射值對(duì)象
    valueOfA := reflect.ValueOf(a)

    //獲取interface{}類型的值圃庭,通過類型斷言轉(zhuǎn)換
    var getA int = valueOfA.Interface().(int)

    //獲取64位的值锄奢,強(qiáng)制類型轉(zhuǎn)換為int類型
    var getB int = int(valueOfA.Int())

    fmt.Println(getA, getB)
}

代碼輸出如下:

1024 1024

代碼說明如下:

  • 第 11 行,聲明一個(gè)變量剧腻,類型為 int拘央,設(shè)置初值為 1024。
  • 第 14 行书在,獲取變量 a 的反射值對(duì)象灰伟,類型為 reflect.Value,這個(gè)過程和 reflect.TypeOf() 類似儒旬。
  • 第 17 行栏账,將 valueOfA 反射值對(duì)象以 interface{} 類型取出,通過類型斷言轉(zhuǎn)換為 int 類型并賦值給 getA义矛。
  • 第 20 行发笔,將 valueOfA 反射值對(duì)象通過 Int 方法,以 int64 類型取出凉翻,通過強(qiáng)制類型轉(zhuǎn)換了讨,轉(zhuǎn)換為原本的 int 類型。

通過反射訪問結(jié)構(gòu)體成員的值

反射值對(duì)象(reflect.Value)提供對(duì)結(jié)構(gòu)體訪問的方法制轰,通過這些方法可以完成對(duì)結(jié)構(gòu)體任意值的訪問前计,如下表所示。

方 法 備 注
Field(i int) Value 根據(jù)索引垃杖,返回索引對(duì)應(yīng)的結(jié)構(gòu)體成員字段的反射值對(duì)象男杈。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
NumField() int 返回結(jié)構(gòu)體成員字段數(shù)量。當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
FieldByName(name string) Value 根據(jù)給定字符串返回字符串對(duì)應(yīng)的結(jié)構(gòu)體字段调俘。沒有找到時(shí)返回零值伶棒,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
FieldByIndex(index []int) Value 多層成員訪問時(shí)旺垒,根據(jù) []int 提供的每個(gè)結(jié)構(gòu)體的字段索引,返回字段的值肤无。 沒有找到時(shí)返回零值先蒋,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)
FieldByNameFunc(match func(string) bool) Value 根據(jù)匹配函數(shù)匹配需要的字段。找到時(shí)返回零值宛渐,當(dāng)值不是結(jié)構(gòu)體或索引超界時(shí)發(fā)生宕機(jī)

下面代碼構(gòu)造一個(gè)結(jié)構(gòu)體包含不同類型的成員竞漾。通過 reflect.Value 提供的成員訪問函數(shù),可以獲得結(jié)構(gòu)體值的各種數(shù)據(jù)窥翩。

反射訪問結(jié)構(gòu)體成員的值:

package main

import (
    "fmt"
    "reflect"
)

//定義結(jié)構(gòu)體
type Student struct {
    Name string
    Age  int

    //嵌入字段
    float32
    bool

    next *Student
}

func main() {

    //值包裝結(jié)構(gòu)體
    rValue := reflect.ValueOf(Student{
        next: &Student{},
    })

    //獲取字段數(shù)量
    fmt.Println("NumField:", rValue.NumField())

    //獲取索引為2的字段(float32字段)
    //注:經(jīng)過測試發(fā)現(xiàn)Field(i)的參數(shù)索引是從0開始的业岁,
    //并且是按照定義的結(jié)構(gòu)體的順序來的,而不是按照字段名字的ASCii碼值來的
    floatField := rValue.Field(2)

    //輸出字段類型
    fmt.Println("Field:", floatField.Type())

    //根據(jù)名字查找字段
    fmt.Println("FieldByName(\"Age\").Type:", rValue.FieldByName("Age").Type())

    //根據(jù)索引查找值中next字段的int字段的值
    fmt.Println("FieldByIndex([]int{4, 0}).Type()", rValue.FieldByIndex([]int{4, 0}).Type())

}

輸出結(jié)果為:

NumField: 5
Field: float32
FieldByName("Age").Type: int
FieldByIndex([]int{4, 0}).Type() string

代碼說明如下:

  • 第 9 行寇蚊,定義結(jié)構(gòu)體笔时,結(jié)構(gòu)體的每個(gè)字段的類型都不一樣。
  • 第 24 行仗岸,實(shí)例化結(jié)構(gòu)體并包裝為 reflect.Value 類型糊闽,成員中包含一個(gè) *Student 的實(shí)例。
  • 第 29 行爹梁,獲取結(jié)構(gòu)體的字段數(shù)量。
  • 第 34 和 37 行提澎,獲取索引為2的字段值(float32 字段)姚垃,并且打印類型。
  • 第 39 行盼忌,根據(jù)Age字符串积糯,查找到 Age 字段的類型。
  • 第 41 行谦纱,[]int{4,0} 中的 4 表示看成,在 Student 結(jié)構(gòu)中索引值為 4 的成員,也就是 next跨嘉。next 的類型為 Student川慌,也是一個(gè)結(jié)構(gòu)體,因此使用 []int{4,0} 中的 0 繼續(xù)在 next 值的基礎(chǔ)上索引祠乃,結(jié)構(gòu)為 Student 中索引值為 0 的 Name 字段梦重,類型為 string。

判斷反射值得空和有效性

IsNil()和IsValid() -- 判斷反射值的空和有效性

反射值對(duì)象(reflect.Value)提供一系列方法進(jìn)行零值和空判定亮瓷,如下表所示琴拧。

<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值對(duì)象的零值和有效性判斷方法</center>

方 法 說 明
IsNil() bool 返回值是否為 nil。如果值類型不是通道(channel)嘱支、函數(shù)蚓胸、接口挣饥、map、指針或 切片時(shí)發(fā)生 panic沛膳,類似于語言層的v== nil操作
IsValid() bool 判斷值是否有效扔枫。 當(dāng)值本身非法時(shí),返回 false于置,例如 reflect Value不包含任何值茧吊,值為 nil 等。

下面的例子將會(huì)對(duì)各種方式的空指針進(jìn)行 IsNil() 和 IsValid() 的返回值判定檢測八毯。同時(shí)對(duì)結(jié)構(gòu)體成員及方法查找 map 鍵值對(duì)的返回值進(jìn)行 IsValid() 判定搓侄,參考下面的代碼。

反射值對(duì)象的零值和有效性判斷:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    //*int的空指針
    var a *int
    fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())

    //nil值
    fmt.Println("nil:", reflect.ValueOf(nil).IsValid())

    //*int類型的空指針
    fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())

    //實(shí)例化一個(gè)結(jié)構(gòu)體
    s := struct {}{}

    //嘗試從結(jié)構(gòu)體中查找一個(gè)不存在的字段
    fmt.Println("不存在的結(jié)構(gòu)體成員:", reflect.ValueOf(s).FieldByName("").IsValid())

    //嘗試從結(jié)構(gòu)體中查找一個(gè)不存在的方法
    fmt.Println("不存在的方法:", reflect.ValueOf(s).MethodByName("").IsValid())

    //實(shí)例化一個(gè)map
    m := map[int]int{}

    //嘗試從map中查找一個(gè)不存在的鍵
    fmt.Println("不存在的鍵:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

輸出結(jié)果:

var a *int: true
nil: false
(*int)(nil): false
不存在的結(jié)構(gòu)體成員: false
不存在的方法: false
不存在的鍵: false

代碼說明如下:

  • 第 11 行话速,聲明一個(gè) *int 類型的指針讶踪,初始值為 nil。
  • 第 12 行泊交,將變量 a 包裝為 reflect.Value 并且判斷是否為空乳讥,此時(shí)變量 a 為空指針,因此返回 true廓俭。
  • 第 15 行云石,對(duì) nil 進(jìn)行 IsValid() 判定(有效性判定),返回 false研乒。
  • 第 18 行汹忠,(*int)(nil) 的含義是將 nil 轉(zhuǎn)換為 int,也就是int 類型的空指針雹熬。此行將 nil 轉(zhuǎn)換為 int 類型宽菜,并取指針指向元素。由于 nil 不指向任何元素竿报,int 類型的 nil 也不能指向任何元素铅乡,值不是有效的。因此這個(gè)反射值使用 Isvalid() 判斷時(shí)返回 false烈菌。
  • 第 21 行阵幸,實(shí)例化一個(gè)結(jié)構(gòu)體。
  • 第 24 行芽世,通過 FieldByName 查找 s 結(jié)構(gòu)體中一個(gè)空字符串的成員侨嘀,如成員不存在,IsValid() 返回 false捂襟。
  • 第 27 行咬腕,通過 MethodByName 查找 s 結(jié)構(gòu)體中一個(gè)空字符串的方法,如方法不存在葬荷,IsValid() 返回 false涨共。
  • 第 30 行纽帖,實(shí)例化一個(gè) map,這種寫法與 make 方式創(chuàng)建的 map 等效举反。
  • 第 33 行懊直,MapIndex() 方法能根據(jù)給定的 reflect.Value 類型的值查找 map,并且返回查找到的結(jié)果火鼻。

IsNil() 常被用于判斷指針是否為空室囊;IsValid() 常被用于判定返回值是否有效。

通過反射修改變量的值

使用 reflect.Value 對(duì)包裝的值進(jìn)行修改時(shí)魁索,需要遵循一些規(guī)則融撞。如果沒有按照規(guī)則進(jìn)行代碼設(shè)計(jì)和編寫,輕則無法修改對(duì)象值粗蔚,重則程序在運(yùn)行時(shí)會(huì)發(fā)生宕機(jī)尝偎。

判斷及獲取元素的相關(guān)方法

使用 reflect.Value 取元素、取地址及修改值的屬性方法請(qǐng)參考下表鹏控。

<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值對(duì)象的判定及獲取元素的方法</center>

方法名 備 注
Elem() Value 取值指向的元素值致扯,類似于語言層*操作。當(dāng)值類型不是指針或接口時(shí)發(fā)生宕 機(jī)当辐,空指針時(shí)返回 nil 的 Value
Addr() Value 對(duì)可尋址的值返回其地址抖僵,類似于語言層&操作。當(dāng)值不可尋址時(shí)發(fā)生宕機(jī)
CanAddr() bool 表示值是否可尋址
CanSet() bool 返回值能否被修改缘揪。要求值可尋址且是導(dǎo)出的字段

值修改相關(guān)方法

使用 reflect.Value 修改值的相關(guān)方法如下表所示裆针。

<center style="margin: 0px; padding: 0px; color: rgb(0, 0, 0); font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">反射值對(duì)象修改值的方法</center>

Set(x Value) 將值設(shè)置為傳入的反射值對(duì)象的值
Setlnt(x int64) 使用 int64 設(shè)置值。當(dāng)值的類型不是 int寺晌、int8、int16澡刹、 int32呻征、int64 時(shí)會(huì)發(fā)生宕機(jī)
SetUint(x uint64) 使用 uint64 設(shè)置值。當(dāng)值的類型不是 uint罢浇、uint8陆赋、uint16、uint32嚷闭、uint64 時(shí)會(huì)發(fā)生宕機(jī)
SetFloat(x float64) 使用 float64 設(shè)置值攒岛。當(dāng)值的類型不是 float32、float64 時(shí)會(huì)發(fā)生宕機(jī)
SetBool(x bool) 使用 bool 設(shè)置值胞锰。當(dāng)值的類型不是 bod 時(shí)會(huì)發(fā)生宕機(jī)
SetBytes(x []byte) 設(shè)置字節(jié)數(shù)組 []bytes值灾锯。當(dāng)值的類型不是 []byte 時(shí)會(huì)發(fā)生宕機(jī)
SetString(x string) 設(shè)置字符串值。當(dāng)值的類型不是 string 時(shí)會(huì)發(fā)生宕機(jī)

以上方法嗅榕,在 reflect.Value 的 CanSet 返回 false 仍然修改值時(shí)會(huì)發(fā)生宕機(jī)顺饮。

在已知值的類型時(shí)吵聪,應(yīng)盡量使用值對(duì)應(yīng)類型的反射設(shè)置值。

值可修改條件之一:可被尋址

通過反射修改變量值的前提條件之一:這個(gè)值必須可以被尋址兼雄。簡單地說就是這個(gè)變量必須能被修改吟逝。示例代碼如下:

package main

import "reflect"

func main() {

    //聲明整形變量a并賦初值
    var a int = 1024

    //獲取變量a的反射值對(duì)象
    rValue := reflect.ValueOf(a)

    //嘗試將a修改為1(此處會(huì)崩潰)
    rValue.SetInt(1)
}

程序運(yùn)行崩潰,打印錯(cuò)誤

panic: reflect: reflect.Value.SetInt using unaddressable value

報(bào)錯(cuò)意思是:SetInt正在使用一個(gè)不能被尋址的值赦肋。從 reflect.ValueOf 傳入的是 a 的值块攒,而不是 a 的地址,這個(gè) reflect.Value 當(dāng)然是不能被尋址的佃乘。將代碼修改一下囱井,重新運(yùn)行:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    //聲明整形變量a并賦初值
    var a int = 1024

    //獲取變量a的反射值對(duì)象
    rValue := reflect.ValueOf(&a)

    //取出a地址的元素(a的值)
    rValue = rValue.Elem()

    //嘗試將a修改為1
    rValue.SetInt(1)

    //打印a的值
    fmt.Println(rValue.Int())
}

代碼輸出

1

下面是對(duì)代碼的分析:

  • 第 14 行中,將變量 a 取值后傳給 reflect.ValueOf()恕稠。此時(shí) reflect.ValueOf() 返回的 valueOfA 持有變量 a 的地址琅绅。
  • 第 17 行中,使用 reflect.Value 類型的 Elem() 方法獲取 a 地址的元素鹅巍,也就是 a 的值千扶。reflect.Value 的 Elem() 方法返回的值類型也是 reflect.Value。
  • 第 20 行骆捧,此時(shí) rValue 表示的是 a 的值且可以尋址澎羞。使用 SetInt() 方法設(shè)置值時(shí)不再發(fā)生崩潰。
  • 第 23 行敛苇,正確打印修改的值妆绞。

提示

當(dāng) reflect.Value 不可尋址時(shí),使用 Addr() 方法也是無法取到值的地址的枫攀,同時(shí)會(huì)發(fā)生宕機(jī)括饶。雖然說 reflect.Value 的 Addr() 方法類似于語言層的&操作;Elem() 方法類似于語言層的*操作来涨,但并不代表這些方法與語言層操作等效图焰。

值可修改條件之一:被導(dǎo)出

結(jié)構(gòu)體成員中,如果字段沒有被導(dǎo)出蹦掐,即便不使用反射也可以被訪問技羔,但不能通過反射修改,代碼如下:

package main

import "reflect"

func main() {

    type dog struct {
        legCount int
    }

    //獲取dog實(shí)例的反射值對(duì)象
    valueOfDog := reflect.ValueOf(&dog{})

    valueOfDog = valueOfDog.Elem()

    //獲取legCount字段的值
    vLegCount := valueOfDog.FieldByName("legCount")

    //嘗試設(shè)置legCount的值(這里會(huì)發(fā)生崩潰)
    vLegCount.SetInt(4)
}

程序發(fā)生崩潰卧抗,報(bào)錯(cuò):

panic: reflect: reflect.Value.SetInt using value obtained using unexported field

報(bào)錯(cuò)的意思是:SetInt() 使用的值來自于一個(gè)未導(dǎo)出的字段藤滥。

為了能修改這個(gè)值,需要將該字段導(dǎo)出社裆。將 dog 中的 legCount 的成員首字母大寫拙绊,導(dǎo)出 LegCount 讓反射可以訪問,修改后的代碼如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    type dog struct {
        LegCount int
    }

    //獲取dog實(shí)例的反射值對(duì)象
    valueOfDog := reflect.ValueOf(&dog{})

  //// 取出dog實(shí)例地址的元素
    valueOfDog = valueOfDog.Elem()

    //獲取legCount字段的值
    vLegCount := valueOfDog.FieldByName("LegCount")

    //嘗試設(shè)置legCount的值
    vLegCount.SetInt(4)

    fmt.Println(vLegCount.Int())
}

代碼輸出如下:

4

代碼說明如下:

  • 第 10 行,將 LegCount 首字母大寫導(dǎo)出該字段时呀。
  • 第 15 行张漂,獲取 dog 實(shí)例指針的反射值對(duì)象。
  • 第 19 行谨娜,取 dog 實(shí)例的指針元素航攒,也就是 dog 的實(shí)例。
  • 第 21 行趴梢,取 dog 結(jié)構(gòu)體中 LegCount 字段的成員值漠畜。
  • 第 24 行,修改該成員值坞靶。
  • 第 26 行憔狞,打印該成員值。

值的修改從表面意義上叫可尋址彰阴,換一種說法就是值必須“可被設(shè)置”瘾敢。那么,想修改變量值尿这,一般的步驟是:

  1. 取這個(gè)變量的地址或者這個(gè)變量所在的結(jié)構(gòu)體已經(jīng)是指針類型簇抵。
  2. 使用 reflect.ValueOf 進(jìn)行值包裝。
  3. 通過 Value.Elem() 獲得指針值指向的元素值對(duì)象(Value)射众,因?yàn)橹祵?duì)象(Value)內(nèi)部對(duì)象為指針時(shí)碟摆,使用 set 設(shè)置時(shí)會(huì)報(bào)出宕機(jī)錯(cuò)誤。
  4. 使用 Value.SetXXX 設(shè)置值叨橱。

通過類型信息創(chuàng)建實(shí)例

當(dāng)已知 reflect.Type 時(shí)典蜕,可以動(dòng)態(tài)地創(chuàng)建這個(gè)類型的實(shí)例,實(shí)例的類型為指針罗洗。例如 reflect.Type 的類型為 int 時(shí)愉舔,創(chuàng)建 int 的指針,即*int伙菜,代碼如下:

package main

import (
    "fmt"
    "reflect"
)

func main() {

    var a int

    //取變量a的反射類型對(duì)象
    typeOfA := reflect.TypeOf(a)

    //根據(jù)反射類型對(duì)象創(chuàng)建類型實(shí)例
    aIns := reflect.New(typeOfA)

    //輸出Value的類型和種類
    fmt.Println(aIns.Type(), aIns.Kind())
}

代碼輸出結(jié)果如下

*int ptr

代碼說明如下:

  • 第 13 行轩缤,獲取變量 a 的反射類型對(duì)象。
  • 第 16 行仇让,使用 reflect.New() 函數(shù)傳入變量 a 的反射類型對(duì)象,創(chuàng)建這個(gè)類型的實(shí)例值躺翻,值以 reflect.Value 類型返回丧叽。這步操作等效于:new(int),因此返回的是 *int 類型的實(shí)例公你。
  • 第 19 行踊淳,打印 aIns 的類型為 *int,種類為指針。

通過反射調(diào)用函數(shù)

如果反射值對(duì)象(reflect.Value)中值的類型為函數(shù)時(shí)迂尝,可以通過 reflect.Value 調(diào)用該函數(shù)脱茉。使用反射調(diào)用函數(shù)時(shí),需要將參數(shù)使用反射值對(duì)象的切片 []reflect.Value 構(gòu)造后傳入 Call() 方法中垄开,調(diào)用完成時(shí)琴许,函數(shù)的返回值通過 []reflect.Value 返回。

下面的代碼聲明一個(gè)加法函數(shù)溉躲,傳入兩個(gè)整型值榜田,返回兩個(gè)整型值的和。將函數(shù)保存到反射值對(duì)象(reflect.Value)中锻梳,然后將兩個(gè)整型值構(gòu)造為反射值對(duì)象的切片([]reflect.Value)箭券,使用 Call() 方法進(jìn)行調(diào)用。

反射調(diào)用函數(shù):

package main

import (
    "fmt"
    "reflect"
)

//普通函數(shù)
func add(a, b int) int {
    return a + b
}

func main() {

    //將函數(shù)包裝為反射值對(duì)象
    funcValue := reflect.ValueOf(add)

    //構(gòu)造函數(shù)參數(shù)疑枯,傳入兩個(gè)整形值
    paramList := []reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)}

    //反射調(diào)用函數(shù)
    retList := funcValue.Call(paramList)

    fmt.Println(retList[0].Int())
}

代碼說明如下:

  • 第 9~12 行辩块,定義一個(gè)普通的加法函數(shù)。
  • 第 17 行荆永,將 add 函數(shù)包裝為反射值對(duì)象废亭。
  • 第 20 行,將 10 和 20 兩個(gè)整型值使用 reflect.ValueOf 包裝為 reflect.Value屁魏,再將反射值對(duì)象的切片 []reflect.Value 作為函數(shù)的參數(shù)滔以。
  • 第 23 行,使用 funcValue 函數(shù)值對(duì)象的 Call() 方法氓拼,傳入?yún)?shù)列表 paramList 調(diào)用 add() 函數(shù)你画。
  • 第 26 行,調(diào)用成功后桃漾,通過 retList[0] 取返回值的第一個(gè)參數(shù)坏匪,使用 Int 取返回值的整數(shù)值。

提示

反射調(diào)用函數(shù)的過程需要構(gòu)造大量的 reflect.Value 和中間變量撬统,對(duì)函數(shù)參數(shù)值進(jìn)行逐一檢查适滓,還需要將調(diào)用參數(shù)復(fù)制到調(diào)用函數(shù)的參數(shù)內(nèi)存中。調(diào)用完畢后恋追,還需要將返回值轉(zhuǎn)換為 reflect.Value凭迹,用戶還需要從中取出調(diào)用值。因此苦囱,反射調(diào)用函數(shù)的性能問題尤為突出嗅绸,不建議大量使用反射函數(shù)調(diào)用。

通過反射調(diào)用方法

調(diào)用方法和調(diào)用函數(shù)是一樣的撕彤,只不過結(jié)構(gòu)體需要先通過rValue.Method()先獲取方法再調(diào)用鱼鸠,請(qǐng)看如下示例:

package main

import (
    "fmt"
    "reflect"
)

type MyMath struct {
    Pi float64
}

//普通函數(shù)
func (myMath MyMath) Sum(a, b int) int {
    return a + b
}

func (myMath MyMath) Dec(a, b int) int {
    return a - b
}

func main() {

    var myMath = MyMath{Pi:3.14159}

    //獲取myMath的值對(duì)象
    rValue := reflect.ValueOf(myMath)

    //獲取到該結(jié)構(gòu)體有多少個(gè)方法
    //numOfMethod := rValue.NumMethod()

    //構(gòu)造函數(shù)參數(shù),傳入兩個(gè)整形值
    paramList := []reflect.Value{reflect.ValueOf(30), reflect.ValueOf(20)}

    //調(diào)用結(jié)構(gòu)體的第一個(gè)方法Method(0)
    //注意:在反射值對(duì)象中方法索引的順序并不是結(jié)構(gòu)體方法定義的先后順序
    //而是根據(jù)方法的ASCII碼值來從小到大排序,所以Dec排在第一個(gè)蚀狰,也就是Method(0)
    result := rValue.Method(0).Call(paramList)

    fmt.Println(result[0].Int())

}

代碼輸出結(jié)果為:

10
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愉昆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子麻蹋,更是在濱河造成了極大的恐慌跛溉,老刑警劉巖诡蜓,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件览爵,死亡現(xiàn)場離奇詭異础米,居然都是意外死亡拇厢,警方通過查閱死者的電腦和手機(jī)册赛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門号枕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慕趴,“玉大人碴卧,你說我怎么就攤上這事深夯《陡瘢” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵咕晋,是天一觀的道長雹拄。 經(jīng)常有香客問我,道長掌呜,這世上最難降的妖魔是什么滓玖? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮质蕉,結(jié)果婚禮上势篡,老公的妹妹穿的比我還像新娘。我一直安慰自己模暗,他們只是感情好禁悠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兑宇,像睡著了一般碍侦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隶糕,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天瓷产,我揣著相機(jī)與錄音,去河邊找鬼枚驻。 笑死濒旦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的测秸。 我是一名探鬼主播疤估,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霎冯!你這毒婦竟也來了铃拇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤沈撞,失蹤者是張志新(化名)和其女友劉穎慷荔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缠俺,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡显晶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壹士。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片磷雇。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖躏救,靈堂內(nèi)的尸體忽然破棺而出唯笙,到底是詐尸還是另有隱情,我是刑警寧澤盒使,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布崩掘,位于F島的核電站,受9級(jí)特大地震影響少办,放射性物質(zhì)發(fā)生泄漏苞慢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一英妓、第九天 我趴在偏房一處隱蔽的房頂上張望挽放。 院中可真熱鬧,春花似錦鞋拟、人聲如沸骂维。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽航闺。三九已至,卻和暖如春猴誊,著一層夾襖步出監(jiān)牢的瞬間潦刃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工懈叹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乖杠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓澄成,卻偏偏與公主長得像胧洒,于是被迫代替她去往敵國和親畏吓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Go語言提供了 reflect 包來訪問程序的反射信息卫漫。 輸出結(jié)果: P struct 種類(Kind)指的是對(duì)象...
    落落小方地發(fā)卡閱讀 210評(píng)論 0 0
  • Go reflect 反射實(shí)例解析 ——教壞小朋友系列 0 FBI WARNING 對(duì)于本文內(nèi)容菲饼,看懂即可,完全不...
    楊浥塵閱讀 695評(píng)論 0 3
  • 一列赎、認(rèn)識(shí)反射 維基百科中的定義:在計(jì)算機(jī)科學(xué)中宏悦,反射是指計(jì)算機(jī)程序在運(yùn)行時(shí)(Run time)可以訪問、檢測和修改...
    Every_dawn閱讀 1,580評(píng)論 0 0
  • 第一次知道反射的時(shí)候還是許多年前在學(xué)校里玩 C# 的時(shí)候包吝。那時(shí)總是弄不清楚這個(gè)復(fù)雜的玩意能有什么實(shí)際用途……然后發(fā)...
    勿以浮沙筑高臺(tái)閱讀 1,125評(píng)論 0 9
  • 近期在公司實(shí)習(xí)饼煞,參與了公司的一個(gè)分布式的應(yīng)用服務(wù)系統(tǒng)。系統(tǒng)采用Golang語言作為系統(tǒng)的開發(fā)語言诗越,在開發(fā)過程中采用...
    flytutu閱讀 1,929評(píng)論 0 1