本篇文章带大家主要来聊聊Golang中反射,希望对你有新的认知。
虽然很多人使用 Go 语言有一定时间了,甚至有的使用了 1 年 2 年,然后对于 Go 语言中的反射还是模棱两可,使用起来的时候,心里也不是非常有底气。【相关推荐:Go视频教程、编程教学】
更有甚者,几乎不使用反射,当然,也不是什么错,在工作中能用最简单最高效,又可扩展,性能还好的方式来进行处理自然是最 nice ,没有必要去生搬硬套一些高级用法,毕竟工作不是我们的试炼场,可以自己下来多多实验,本次就来好好看看如何去玩反射
文章分别从如下五个方面来聊
- 反射是什么
- 反射的规则
- 使用案例并灵活运用
- 反射原理
- 总结
简单来看反射是什么
简单来看,反射就是在程序运行时期对程序本身进行访问和修改的能力,例如在程序运行时,可以修改程序的字段名称,字段值,还可以给程序提供接口访问的信息等等
这是 Go 语言中提供的一种机制,我们可以在 Go 语言公共库中可以看到很多关于 reflect 的使用位置
例如常用的 fmt 包,常用的 json 序列化和反序列化,自然前面我们说到的 gorm 库自然也是使用了反射的
可是我们一般为什么要使用反射呢?
根据反射的能力,自然是因为我们提供的接口并不知道传入的数据类型会是什么样的, 只有当程序运行的时候才知道具体的数据类型
但是我们编码的时候又期望去校验程序运行时传入的类型会是什么样的(例如 json 的序列化)并对其这种具体的数据进行操作,这个时候,咱们就需要用到反射的能力了
所以对于使用到反射的地方,你都能看到 interface{} 是不是就不奇怪了呢?
正是因为不确定传入的数据类型会是什么样的,所以才设计成 interface{} ,那么如果对于 interface 有还不清楚他的特点和使用方式的,可以查看历史文章:
先关注反射的规则
首先关注反射的三个重要的定律,知道规则之后,我们按照规则玩就不会有什么问题,只有当我们不清楚规则,总是触发条款的时候,才会出现奇奇怪怪的问题
反射是可以将 接口类型的变量 转换成 反射类型的对象
- 反射可以将 反射类型的对象 转换成 接口类型的变量
- 我们在运行时要去修改的 反射类型的对象 ,那么要求这个对象对应的值是要可写的
对于上述 3 个规则也是比较好理解,还记的之前我们说过的 unsafe 包里面的指针吗?
都是将我们常用的数据类型,转换成包(例如 unsafe包,或者 reflect 包)里面的指定数据类型,然后再按照包里面的规则进行修改数据
相当于,换个马甲,就可以进行不同的操作了
关注使用案例并灵活运用
一般咱们先会基本的应用,再去研究他的原理,研究他为什么可以这样用,慢慢的才能理解的更加深刻
对于定律一,将 接口类型的变量 转换成 反射类型的对象
实际上此处说的 接口类型的变量 我们可以传入任意数据类型的变量,例如 int, float, string ,map, slice, struct
等等
反射類型的對象這裡就可以理解成reflect 反射包中的reflect.Type
和 reflect.Value
對象,可以透過reflect 包中提供的TypeOf 和ValueOf 函數得到
其中reflect.Type
實際上是一個interface ,他裡麵包含了各種介面需要實現,它裡面提供了關於類型相關的資訊
其中如下圖可以查看到reflect.Type
的所有方法,其中
- 綠色的是所有數據類型都是可以呼叫的
- 紅色的是函數型別資料可以呼叫的
- 黑色的是Map,數組Array,通道Chan,指標Ptr 或切片Slice 可以呼叫的
- 藍色的是結構體呼叫的
- 黃色的是通道channel 類型呼叫的
reflect.Value
實際上是一個struct,根據這個struct 還關聯了一組方法,這裡面存放了數據類型和具體的數據,透過查看其數據結構就可以看出
type Value struct { typ *rtype ptr unsafe.Pointer flag }
看到此處的unsafe.Pointer是不是很熟悉,底層自然就可以將unsafe.Pointer
轉換成uintptr
,然後再修改其資料後,再轉換回來,對於Go 指標不太熟悉的可以檢視這文章:
寫一個簡單的demo 就可以簡單的取得到變數的資料型別和值
func main() { var demoStr string = "now reflect" fmt.Println("type:", reflect.TypeOf(demoStr)) fmt.Println("value:", reflect.ValueOf(demoStr)) }
對於定律二,將反射類型的物件轉換成介面類型的變數
我們可以透過將reflect.Value
類型轉換成我們特定的資料類型,因為reflect. Value
中有對應的typ *rtype
以及ptr unsafe.Pointer
例如我們可以透過reflect.Value
物件的interface () 方法來處理
func main() { var demoStr string = "now reflect" fmt.Println("type:", reflect.TypeOf(demoStr)) fmt.Println("value:", reflect.ValueOf(demoStr)) var res string res = reflect.ValueOf(demoStr).Interface().(string) fmt.Println("res == ",res) }
#對於定律三,修改反射類型的物件
首先我們看上書的demo 程式碼,傳入TypeOf 和ValueOf 的變數其實也是拷貝,那麼如果期望在反射型別的物件中修改其值,那麼就需要拿到具體變數的位址然後再進行修改,前提是這個變數是可寫的
舉例你就能明白
func main() { var demoStr string = "now reflect" v := reflect.ValueOf(demoStr) fmt.Println("is canset ", v.CanSet()) //v.SetString("hello world") // 会panic }
可以先呼叫reflect.Value
物件的CanSet
查看是否可寫,如果是可寫的,我們再寫,如果不可寫就不要寫了,否則會panic
那麼傳入變數的位址就可以修改了? ?
傳入位址的思路沒有毛病,但是我們去設定值的方式有問題,因此也會出現上述的panic 情況
此處仔細看能夠明白,反射的物件v 自然是不可修改的,我們應該找到reflect.Value
裡面具體具體的資料指針,那麼才是可以修改的,可以使用reflect.Value
的Elem 方法
稍微複雜一點的
看上了上述案例可能會覺得那麼簡單的案例,一演示就ok,但是工作中一用就崩潰,那自然還是沒有融會貫通,說明還沒有消化好,再來一個工作中的例子
- 一個結構體裡面有map ,map 中的key 是string,value 是[]string
- 需求是存取結構體中hobby 欄位對應的map key 為sport 的切片的第1 個元素,並將其修改為hellolworld
type RDemo struct { Name string Age int Money float32 Hobby map[string][]string } func main() { tmp := &RDemo{ Name: "xiaomiong", Age: 18, Money: 25.6, Hobby: map[string][]string{ "sport": {"basketball", "football"}, "food": {"beef"}, }, } v := reflect.ValueOf(tmp).Elem() // 拿到结构体对象 h := v.FieldByName("Hobby") // 拿到 Hobby 对象 h1 := h.MapKeys()[0] // 拿到 Hobby 的第 0 个key fmt.Println("key1 name == ",h1.Interface().(string)) sli := h.MapIndex(h1) // 拿到 Hobby 的第 0 个key对应的对象 str := sli.Index(1) // 拿到切片的第 1 个对象 fmt.Println(str.CanSet()) str.SetString("helloworld") fmt.Println("tmp == ",tmp) }
可以看到上述案例运行之后有时可以运行成功,有时会出现 panic 的情况,相信细心的 xdm 就可以看出来,是因为 map 中的 key 是 无序的导致的,此处也提醒一波,使用 map 的时候要注意这一点
看上述代码,是不是就能够明白咱们使用反射去找到对应的数据类型,然后按照数据类型进行处理数据的过程了呢
有需要的话,可以慢慢的去熟练反射包中涉及的函数,重点是要了解其三个规则,对象转换方式,访问方式,以及数据修改方式
反射原理
那么通过上述案例,可以知道关于反射中数据类型和数据指针对应的值是相当重要的,不同的数据类型能够用哪些函数这个需要注意,否则用错直接就会 panic
TypeOf
来看 TypeOf 的接口中涉及的数据结构
在 reflect 包中 rtype
是非常重要的,Go 中所有的类型都会包含这个结构,所以咱们反射可以应用起来,结构如下
// rtype must be kept in sync with ../runtime/type.go:/^type._type. type rtype struct { size uintptr ptrdata uintptr hash uint32 tflag tflag align uint8 fieldAlign uint8 kind uint8 equal func(unsafe.Pointer, unsafe.Pointer) bool gcdata *byte str nameOff ptrToThis typeOff }
其中可以看到此处的 rtype
的结构保持和 runtime/type.go
一致 ,都是关于数据类型的表示,以及对应的指针,关于这一块的说明和演示可以查看文末的 interface{} 处的内容
ValueOf
从 ValueOf 的源码中,我们可以看到,重要的是 emptyInterface 结构
// emptyInterface is the header for an interface{} value.type emptyInterface struct { typ *rtype word unsafe.Pointer }复制代码
emptyInterface 结构中有 rtype
类型的指针, word 自然是对应的数据的地址了
reflect.Value
对象中的方法也是非常的多,用起来和上述说到的 reflect.Type
接口中的功能类似
关于源码中涉及到的方法,就不再过多的赘述了,更多的还是需要自己多多实践才能体会的更好
殊不知,此处的 reflect.Value
也是可以转换成 reflect.Type
,可以查看源码中 reflect\value.go
的 func (v Value) Type() Type {
其中 reflect.Value
,reflect.Type
,和任意数据类型
可以相互这样来转换
如下图:
总结
至此,关于反射就聊到这里,一些关于源码的细节并没有详细说,更多的站在一个使用者的角度去看反射需要注意的点
关于反射,大多的人是建议少用,因为是会影响到性能,不过如果不太关注这一点,那么用起来还是非常方便的
高级功能自然也是双刃剑,你用不好就会 panic,如果你期望去使用他,那么就去更多的深入了解和一步一步的吃透他吧
大道至简,反射三定律,活学活用
更多编程相关知识,请访问:编程视频!!
以上是一文詳解Golang中的反射的詳細內容。更多資訊請關注PHP中文網其他相關文章!

掌握Go語言中的strings包可以提高文本處理能力和開發效率。 1)使用Contains函數檢查子字符串,2)用Index函數查找子字符串位置,3)Join函數高效拼接字符串切片,4)Replace函數替換子字符串。注意避免常見錯誤,如未檢查空字符串和大字符串操作性能問題。

你應該關心Go語言中的strings包,因為它能簡化字符串操作,使代碼更清晰高效。 1)使用strings.Join高效拼接字符串;2)用strings.Fields按空白符分割字符串;3)通過strings.Index和strings.LastIndex查找子串位置;4)用strings.ReplaceAll進行字符串替換;5)利用strings.Builder進行高效字符串拼接;6)始終驗證輸入以避免意外結果。

thestringspackageingoisesential forefficientstringManipulation.1)itoffersSimpleyetpoperfulfunctionsFortaskSlikeCheckingSslingSubstringsStringStringsStringsandStringsN.2)ithandhishiCodeDewell,withFunctionsLikestrings.fieldsfieldsfieldsfordsforeflikester.fieldsfordsforwhitespace-fieldsforwhitespace-separatedvalues.3)3)

WhendecidingbetweenGo'sbytespackageandstringspackage,usebytes.Bufferforbinarydataandstrings.Builderforstringoperations.1)Usebytes.Bufferforworkingwithbyteslices,binarydata,appendingdifferentdatatypes,andwritingtoio.Writer.2)Usestrings.Builderforstrin

Go的strings包提供了多種字符串操作功能。 1)使用strings.Contains檢查子字符串。 2)用strings.Split將字符串分割成子字符串切片。 3)通過strings.Join合併字符串。 4)用strings.TrimSpace或strings.Trim去除字符串首尾的空白或指定字符。 5)用strings.ReplaceAll替換所有指定子字符串。 6)使用strings.HasPrefix或strings.HasSuffix檢查字符串的前綴或後綴。

使用Go語言的strings包可以提升代碼質量。 1)使用strings.Join()優雅地連接字符串數組,避免性能開銷。 2)結合strings.Split()和strings.Contains()處理文本,注意大小寫敏感問題。 3)避免濫用strings.Replace(),考慮使用正則表達式進行大量替換。 4)使用strings.Builder提高頻繁拼接字符串的性能。

Go的bytes包提供了多種實用的函數來處理字節切片。 1.bytes.Contains用於檢查字節切片是否包含特定序列。 2.bytes.Split用於將字節切片分割成smallerpieces。 3.bytes.Join用於將多個字節切片連接成一個。 4.bytes.TrimSpace用於去除字節切片的前後空白。 5.bytes.Equal用於比較兩個字節切片是否相等。 6.bytes.Index用於查找子切片在largerslice中的起始索引。

theEncoding/binarypackageingoisesenebecapeitProvidesAstandArdArdArdArdArdArdArdArdAndWriteBinaryData,確保Cross-cross-platformCompatibilitiational and handhandlingdifferentendenness.itoffersfunctionslikeread,寫下,寫,dearte,readuvarint,andwriteuvarint,andWriteuvarIntforPreciseControloverBinary


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

禪工作室 13.0.1
強大的PHP整合開發環境

SublimeText3 Linux新版
SublimeText3 Linux最新版

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中