golang 結構體斷言_Golang中的reflect原理

60be8cb92a6cdaf3c6f6ea629bd89367.png

反射(reflect)是在計算機程序運行時,訪問,檢查,修改它自身的一種能力,是元編程的一種形式。在Java等語言中都很好地支持了反射。Golang也實現了反射,主要核心位于reflect包,官方文檔為:

https://golang.org/pkg/reflect/?golang.org

本文將主要介紹Golang中的反射原理和支持的反射操作。

1. reflect原理:結構體與關系

Golang是強類型語言,每一個對象都有具體的靜態類型。為什么說是靜態類型呢?舉個例子,如下代碼:

type MyInt intvar a int
var b MyInt

a和b在Go中會被認為是不同的類型,即不會被隱式轉換。另外,在Golang中的對象其實是同時記錄了兩個信息:變量的真實值,與該變量的類型描述。具體地,interface {}在內部是通過emptyInterface結構體表示的,結構體的定義如下:

type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

其中, typ為類型信息,word為指針。

另外interface{}是一個沒有函數定義的接口定義,Golang中的繼承實現是通過比較函數判斷的。也就是說,所有的結構體都實現了默認接口interface{},這也是為什么所有值都能夠隱式賦值給interface{}的原因。

以上的結構體便是Golang中反射的核心,也就是通過操作該結構來進行反射運算,包括獲取對象的類型信息(rtype)和具體值(word指針),rtype的定義如下:

// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size       uintptrptrdata    uintptr // number of bytes in the type that can contain pointershash       uint32  // hash of type; avoids computation in hash tablestflag      tflag   // extra type information flagsalign      uint8   // alignment of variable with this typefieldAlign uint8   // alignment of struct field with this typekind       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) boolgcdata    *byte   // garbage collection datastr       nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero
}

定義了類型需要的數據。

1.1 reflect中的結構體

在Golang的反射中,另外兩個核心結構體是Type和Value。

Type是描述類型信息的接口,包括:結構體的對齊方式、方法、字段、包路徑、與其他結構體的關系等,具體定義可以參考源碼:

https://golang.org/src/reflect/type.go?s=1310:7552#L27?golang.org

Value是保存了對象的類型、指針和其他元數據,具體定義如下:

type Value struct {// typ holds the type of the value represented by a Value.typ *rtype// Pointer-valued data or, if flagIndir is set, pointer to data.// Valid when either flagIndir is set or typ.pointers() is true.ptr unsafe.Pointer// flag holds metadata about the value.// The lowest bits are flag bits://	- flagStickyRO: obtained via unexported not embedded field, so read-only//	- flagEmbedRO: obtained via unexported embedded field, so read-only//	- flagIndir: val holds a pointer to the data//	- flagAddr: v.CanAddr is true (implies flagIndir)//	- flagMethod: v is a method value.// The next five bits give the Kind of the value.// This repeats typ.Kind() except for method values.// The remaining 23+ bits give a method number for method values.// If flag.kind() != Func, code can assume that flagMethod is unset.// If ifaceIndir(typ), code can assume that flagIndir is set.flag// A method value represents a curried method invocation// like r.Read for some receiver r. The typ+val+flag bits describe// the receiver r, but the flag's Kind bits say Func (methods are// functions), and the top bits of the flag give the method number// in r's type's method table.
}

1.2 reflect對象的關系

reflect中的對象關系如下圖所示,Type和Value稱為反射對象,interface{}和Special Type是應用程序中的對象,其中,Special Type指應用程序中的具體類型。具體關系如下:

d61109ff511c3ef33dd0798aa2c35b32.png
反射對象關系圖

1) 從接口值到反射對象

interface{} -> Type: 通過reflect.TypeOf(interface{})獲得interface的類型信息對象;

interface{} -> Value:通過reflect.ValueOf(interface{})獲得interface的Value反射類型對象;

2) 從反射對象到接口值

Value->interface{}:通過Value.Interface()方法可以獲得值對象Value對應的接口;注意,這里不能夠直接獲得具體類型,如果要獲得具體類型,還需要顯式地進行轉換。例如,

var f float64 = 3.1415
v := reflect.ValueOf(f)   // f 隱式地被轉成了interface{}
y := v.Interface().(float64) // y的類型是float64

其中1)和2)兩條關系也是Golang反射中三條大規則中的前兩條。另外第三條是:

3) 想要修改一個反射對象,那么該值必須是可以被設置的

這個可以一個例子進行說明。如下:

var f float64 = 3.1415
v := reflect.ValueOf(f)   // f 隱式地被轉成了interface{}
v.SetFloat(2.873)      // Error: 發生Panic

以上在最后一行代碼將會拋出異常:

panic: reflect.Value.SetFloat using unaddressable value

也就是說,Value v指向的不是一個可尋址的值,簡單地說就是不是一個地址塊。但是如果改成如下代碼:

var f float64 = 3.1415
v := reflect.ValueOf(&f)   // 傳了f的指針,&f 隱式地被轉成了interface{}
v.SetFloat(2.873)      // 成功修改

綜上,也就是說當Value中管理的值是一個可被尋址的值那么改置便是一個可被修改的Value。

或者換一個方式去理解,在Golang中方法調用是值傳遞,然后,假如我們想要該一個方法中修改某一個對象的值,那么我們應該將指向該值的指針傳入,而不是直接將值傳入。

4) Type/Value轉換

Value->Type:可以通過Value.Type()方法獲得;而Type->Value是指創建一個Type的實例對象,則可以通過reflect.New(typ)等方法創建。

2. reflect中的結構體與方法

這里分五個維度進行介紹reflect中的結構和方法,便于理解反射的使用方法。這些操作最終都會落到前面定義的結構體emptyInterface,除在外層封裝中變能夠確定的方法外。

2.1 結構體

reflect中的結構體主要包括:Type,Value,ChanDir,Kind,MapIter,Method,SelectCase,SelectDir,SliceHeader,StringHeader,StructField,StructTag,ValueError等。其中,Type和Value之前已經介紹過了。

  • ChanDir:管道的方向,有三個值:RecvDir/SendDir/BothDir,分別為接受,發送,雙向;
  • Kind:Type中的類型信息,包括:Invalid, Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Float32, Float64, Complex64, Complex128, Array, Chan, Func, Interface, Map, Ptr, Slice, String, Struct, UnsafePointer,
  • MapIter:Map的迭代器,包括三個方法:Key、Value、Next
  • Method:描述方法的信息,包括:方法名,包路徑,類型,函數,所處的下表;
  • SelectCase:描述select 操作的信息,case的方向SelectDir,使用的Channel,發送的值Send;
  • SelectDir:描述SelectCase中的方向,有三個值:SelectSend/SelectRecv/SelectDefault
  • SliceHeader:描述切片Slice的信息,包括指針,長度,容量;
  • StringHeader:描述字符串string的信息,包括指針,長度;
  • StructField:描述結構體中的域field中的信息,包括:域名,包路徑,類型,標簽Tag,在結構體中的偏移量offset,Type.FieldByIndex中的下標index,是否是匿名;
  • StructTag:描述標簽信息,有兩個方法:Get、Lookup
  • ValueError:在調用一個Value不支持的方法時會報錯,并記錄到ValueError中。

2.2 reflect靜態方法

reflect的靜態方法主要用于反射對象的的操作,包括如下:

  • reflect.Copy(dst, src Value) int:將src對象(Slice或Array)復制給dst對象,返回復制的個數;
  • func DeepEqual(x, y interface{}) bool:比較兩個對象是否是“深度相等”。具體如何比較可以參考:https://golang.org/pkg/reflect/#DeepEqual
  • func Swapper(slice interface{}) func(i, j int):生成一個Swapper交換方法,必須為slice;

2.3 域Type相關的方法

該類方法主要定義在https://golang.org/src/reflect/type.go?s=78900:78939#L2811中,包括,返回值都是Type:

  • reflect.ArrayOf:創建一個指定Type和個數的數組;
  • reflect.ChanOf:創建一個類型和方向的管道類型;
  • reflect.FuncOf:創建一個指定輸入/輸出/是否可變(variadic)的函數定義;
  • reflect.MapOf:創建一個指定key/value類型的Map類型;
  • reflect.PtrTo:創建一個類型的指針;
  • reflect.SliceOf:創建一個類型的Slice類型;
  • reflect.StructOf:創建一個指定StructField 列表的結構體定義類型;
  • reflect.TypeOf:獲得interface的類型;

2.4 值Value相關靜態方法

該類方法主要定義在https://golang.org/src/reflect/value.go?s=60334:60372#L2014中,包括:

  • reflect.Append:將值append到一個Slice中,并且返回結果;
  • reflect.AppendSlice:將一個slice append到slice中;
  • reflect.Indirect:返回該Value的指向對象,如果是nil,返回零值,如果非指針,返回該值;
  • reflect.MakeChan:創建一個執行類型和大小的channel;
  • reflect.MakeFunc:在指定類型上創建一個指定定義的函數;
  • reflect.MakeMap:創建一個指定類型的map;
  • reflect.MakeMapWithSize:同上,指定大小;
  • reflect.MakeSlice:創建Slice;
  • reflect.New:創建一個指定類型的實例對象;
  • reflect.NewAt:指定了指針類型?
  • reflect.Select:創建一個select操作,需指定SelectCase
  • reflect.ValueOf:獲得接口interface{}的Value;
  • reflect.Zero:創建一個指定類型的零值;

2.5 Value的實例方法

這里將不一一介紹Value的實例方法,大致可以分成三類:

  • 判斷性方法:判斷是否具有某些特性/能力;
  • 訪問性方法:訪問值的一些屬性;
  • 修改性方法:修改Value中特性/值的方法;

這里介紹一個函數Elem(),該函數返回的是interface{}中包含的值或指針指向的值,如果value的類型不是reflect.Ptr,那么將返回零值。

具體如下:

func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Call(in []Value) []Value// 最后會進入匯編代碼進行方法調用
func (v Value) CallSlice(in []Value) []Value
func (v Value) CanAddr() bool
func (v Value) CanInterface() bool
func (v Value) CanSet() bool
func (v Value) Cap() int
func (v Value) Close()
func (v Value) Complex() complex128
func (v Value) Convert(t Type) Value
func (v Value) Elem() Value
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Float() float64
func (v Value) Index(i int) Value
func (v Value) Int() int64
func (v Value) Interface() (i interface{})
func (v Value) InterfaceData() [2]uintptr
func (v Value) IsNil() bool
func (v Value) IsValid() bool
func (v Value) IsZero() bool
func (v Value) Kind() Kind
func (v Value) Len() int
func (v Value) MapIndex(key Value) Value
func (v Value) MapKeys() []Value
func (v Value) MapRange() *MapIter
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int
func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool
func (v Value) Pointer() uintptr
func (v Value) Recv() (x Value, ok bool)
func (v Value) Send(x Value)
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
func (v Value) Slice(i, j int) Value
func (v Value) Slice3(i, j, k int) Value
func (v Value) String() string
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Type() Type
func (v Value) Uint() uint64
func (v Value) UnsafeAddr() uintptr

3. 總結

最后簡單總結一下,本文首先介紹了Golang中反射的原理,包括其中的核心結構體和關系;然后介紹了Golang中reflect包下包含的結構體,靜態函數,Type靜態函數,Value靜態函數和Value的實例函數。

參考

https://golang.org/pkg/reflect/https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/

Wenguang Liu:Golang中Routine閉包中的一個坑?zhuanlan.zhihu.com

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/393744.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/393744.shtml
英文地址,請注明出處:http://en.pswp.cn/news/393744.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

「hadoop」cdh5.12離線安裝(未完成)

cdh 5.12.1 安裝 目前還未成功,僅供個人記錄。【下載必備工具】當前系統為win7_x641、vmware虛擬機12.52、ubuntu16.04服務器版本3、windows下的ssh工具,采用git bash4、jdk安裝包jdk-8u144-linux-x64.tar.gz5、mysql java驅動包 mysql-connector-java-5…

轉載 JDK + Android-SDK + Python + MonkeyRunner 的安裝

轉載來自: 小海豚的博客 http://blog.sina.com.cn/u/1295334083 我只是搬運工。。。 JDK Android-SDK Python MonkeyRunner 的安裝 1. Android-SDK介紹2. 安裝 JDK, Android-SDK(包含MonkeyRunner) , Python 3. 設置環境變量4. 驗證是否安裝成功1. …

跟隨器反饋回路電阻_如何將短反饋回路設置為單獨編碼器

跟隨器反饋回路電阻I’ve spent the last couple years as a solo freelance developer. Comparing this experience to previously working in companies, I’ve noticed that those of us who work alone can have fewer iterative opportunities for improvement than devel…

leetcode991. 壞了的計算器(貪心)

在顯示著數字的壞計算器上,我們可以執行以下兩種操作: 雙倍(Double):將顯示屏上的數字乘 2; 遞減(Decrement):將顯示屏上的數字減 1 。 最初,計算器顯示數字…

模塊怎么用_Android 組件化/模塊化 的理解!

作者:前行的烏龜到現在組件化真的不是什么新鮮東西了,大公司都用的滾瓜爛熟,龍飛鳳舞了,也就是現在部分中型項目和小項目在組件化的路上努力。所以同志們,組件化沒玩過的,不熟悉的趕緊搞起來,說…

操作系統基礎

操作系統基礎一個完整的操作系統包括 ( kernel application)內核 應用程序而我們要學習操作系統:Linux操作系統我們平時所用的WINDOWS和MS-DOS都是微軟出的,而Linux不是微軟出的,Linux的最大好處是非商業軟件&#x…

leetcode1247. 交換字符使得字符串相同(貪心)

有兩個長度相同的字符串 s1 和 s2,且它們其中 只含有 字符 “x” 和 “y”,你需要通過「交換字符」的方式使這兩個字符串相同。 每次「交換字符」的時候,你都可以在兩個字符串中各選一個字符進行交換。 交換只能發生在兩個不同的字符串之間…

interop_如何在Blazor中實現JavaScript Interop

interop介紹 (Introduction) In this article, we will learn about JavaScript Interop in Blazor. We will understand what JavaScript Interop is and how we can implement it in Blazor with the help of a sample application.在本文中,我們將學習Blazor中Ja…

Centos 7和 Centos 6開放查看端口 防火墻關閉打開

Centos 7 firewall 命令: 查看已經開放的端口: firewall-cmd --list-ports 開啟端口 firewall-cmd --zonepublic --add-port80/tcp --permanent 命令含義: –zone #作用域 –add-port80/tcp #添加端口,格式為:端口/通訊…

和get redis_SpringBoot整合Redis,你get了嗎?

Our-task介紹本篇博客是我github上our-task:一個完整的清單管理系統的配套教程文檔,這是SpringBootVue開發的前后端分離清單管理工具,仿滴答清單。目前已部署在阿里云ECS上,可進行在線預覽,隨意使用(附詳細…

linux課程設計qq,仿QQ聊天系統課程設計.doc

目錄緒論1一.需求分析11.1軟件功能需求分析21.2 安全需求分析2二.總體設計32.1 軟件結構圖32.2 功能描述32.2.1注冊功能概要42.2.2登錄功能概要42.2.3聊天功能概要52.3 安全設計6三.數據庫設計63.1概念結構設計63.2邏輯結構設計73.3物理結構設…

ocp linux 基礎要點

基本命令: 創建/修改/刪除用戶 useradd/usermod/userdel 創建/修改/刪除用戶組 groupadd/groupmod/groupdel 修改所屬用戶/所屬用戶組 chown/chgrp 修改權限 chmod 創建文件夾 mkdir 創建文件 touch 切換目錄 …

leetcode1386. 安排電影院座位(貪心)

如上圖所示,電影院的觀影廳中有 n 行座位,行編號從 1 到 n ,且每一行內總共有 10 個座位,列編號從 1 到 10 。 給你數組 reservedSeats ,包含所有已經被預約了的座位。比如說,researvedSeats[i][3,8] &…

首席技術執行官_如何在幾分鐘內找到任何首席執行官的電子郵件地址

首席技術執行官by Theo Strauss由西奧斯特勞斯(Theo Strauss) 如何在幾分鐘內找到任何首席執行官的電子郵件地址 (How to find any CEO’s email address in minutes) 銀河電子郵件指南:第一部分 (The Emailer’s Guide To The Galaxy: Part I) I’m 17, so my net…

Linux 查看磁盤或文件夾及文件大小

當磁盤大小超過標準時會有報警提示,這時如果掌握df和du命令是非常明智的選擇。 df可以查看一級文件夾大小、使用比例、檔案系統及其掛入點,但對文件卻無能為力。 du可以查看文件及文件夾的大小。 兩者配合使用,非常有效。比如用df查看哪個…

Python列表基礎

列表:創建列表:list[] 注意:列表里面類型可以是不同的類型 取值:list[2]   替換:注意不要越界(下表超出了可表示范圍) 操作: 合并列表:   list3list2list1 列表的重復:   (list8*3)   判斷元素是否…

樹莓派 觸摸屏_如何用樹莓派搭建一個顆粒物(PM2.5)傳感器

用樹莓派、一個廉價的傳感器和一個便宜的屏幕監測空氣質量。-- Stephan Tetzel(作者)大約一年前,我寫了一篇關于如何使用樹莓派和廉價傳感器測量 空氣質量 的文章。我們這幾年已在學校里和私下使用了這個項目。然而它有一個缺點:由于它基于無線/有線網&a…

shell 25個常用命令

1.列出所有目錄使用量,并按大小排序。 ls|xargs du -h|sort -rn #不遞歸下級目錄使用du -sh2.查看文件排除以#開關和空白行,適合查看配置文件。 egrep -v "^#|^$" filenamesed /#.*$/d; /^ *$/d3.刪除空格和空行。 sed /^$/d filename #刪除空…

tensorflow入門_TensorFlow法律和統計入門

tensorflow入門by Daniel Deutsch由Daniel Deutsch TensorFlow法律和統計入門 (Get started with TensorFlow on law and statistics) What this is about 這是關于什么的 What we will use 我們將使用什么 Get started 開始吧 Shell commands for installing everything you …

centos7 nginx+php5.6+mysql安裝與配置

安裝與配置 php 56的安裝 php的配置寫在 php.ini,可在phpinfo()中查看 //查找已安裝 yum list installed | grep php // php卸載 yum -y remove php56* yum remove httpd* php* 可用的資源:centos 安裝php56nginx nginx php-fpm nginx安裝 sudo rpm -Uv…