<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
反射是語言裡面是非常重要的一個特性,我們經常會看見這個詞,但是對於反射沒有一個很好的理解,主要是因為對於反射的使用場景不太熟悉。
1.型別資訊,元資訊,是預先定義好的,靜態的。
2.值資訊,程式進行過程中,動態變化的。
1.空介面相當於一個容器,能接受任何東西。
2.那怎麼判斷空介面變數儲存的是什麼型別呢?之前有使用過型別斷言,這只是一個比較基礎的方法
3.如果想獲取儲存變數的型別資訊和值資訊就要使用反射機制,所以反射是什麼? 反射就是動態的獲取變數型別資訊和值資訊的機制。
①首先利用的是GO語言裡面的Reflect包
②利用包裡的TypeOf方法可以獲取變數的型別資訊
func reflect_typeof(a interface{}) { t := reflect.TypeOf(a) fmt.Printf("type of a is:%vn", t) k := t.Kind() switch k { case reflect.Int64: fmt.Printf("a is int64n") case reflect.String: fmt.Printf("a is stringn") } }
利用Kind() 可以獲取t的型別,如程式碼所示,這裡可以判斷a是Int64還是string, 像下面一樣使用:
func main() { var x int64 = 3 reflect_example(x) var y string = "hello" reflect_example(y) }
列印結果:
type of a is:int64
a is int64
type of a is:string
a is string
③利用包裡的ValueOf方法可以獲取變數的值資訊
func reflect_value(a interface{}) { v := reflect.ValueOf(a) k := v.Kind() switch k { case reflect.Int64: fmt.Printf("a is Int64, store value is:%dn", v.Int()) case reflect.String: fmt.Printf("a is String, store value is:%sn", v.String()) } }
利用ValueOf方法可以得到變數的值資訊,ValueOf返回的是一個Value結構體型別,有趣的是 可以使用 v.Type() 獲取該變數的型別,和上面reflect.TypeOf() 獲取的結果一樣。
此外,因為值資訊是動態的,所以我們不僅僅可以獲取這個變數的型別,還能取得這個變數裡面儲存的值,利用 v.Int() 、 v.String() 等等就能取得值。如上面的main,呼叫此函數返回的結果:
a is Int64, store value is:3
a is String, store value is:hello
這裡存在一個問題,如果傳進去一個型別,使用了錯誤的解析,那麼將會在執行的時候報錯, 例如將 一個string型別強行的v.Int()。
既然值型別是動態的,能取到儲存的值,同樣可以設定值。在反射裡面有很多set的方法,例如SetFloat、SetInt()、SetString()等可以幫助我們設定值。
下面的例子,我想把 x設定為 6.28,但是會報錯!
func main() { var x float64 = 3.14 v := reflect.ValueOf(x) v.SetFloat(6.28) fmt.Printf("After Set Value is %f", x) }
錯誤結果:
panic: reflect: reflect.Value.SetFloat using unaddressable value
......
結果上說明是不可設定的,為什麼呢? 因為我們的x是一個值型別,而值型別的傳遞是拷貝了一個副本,當 v := reflect.ValueOf(x) 函數通過傳遞一個 x 拷貝建立了 v,那麼 v 的改變並不能更改原始的 x。要想 v 的更改能作用到 x,那就必須傳遞 x 的地址 v = reflect.ValueOf(&x)。修改程式如下:
func main() { var x float64 = 3.14 v := reflect.ValueOf(&x) v.SetFloat(6.28) fmt.Printf("After Set Value is %f", x) }
結果:依然報錯!為什麼傳了地址還報錯?因為&x是地址了,所以它的型別就變了,可以通過v.Type(),看下改變成了什麼:
func main() { var x float64 = 3.14 v := reflect.ValueOf(&x) fmt.Printf("type of v is %v", v.Type()) //列印的結果是:type of v is *float64 }
由程式可以知道,這個返回的是一個指標型別的。所以上面SetFloat才會失敗,那怎麼做?
我們正常的賦值,如果是地址的話,例如下面:一般我們都會對*y進行賦值, *的意思就是往這個地址裡面賦值。
var y *float64 = new(float64) *y = 10.12 fmt.Printf("y = %v", *y)
同樣的,我們在反射裡面也可以取地址,需要通過 Elem() 方法進行取地址。再次修改程式
func main() { var x float64 = 3.14 v := reflect.ValueOf(&x) fmt.Printf("type of v is %vn", v.Type()) v.Elem().SetFloat(6.28) fmt.Printf("After set x is %v", x) }
結果為:
type of v is *float64
After set x is 6.28
1.獲取結構體的欄位
我們可以通過上面的方法判斷一個變數是不是結構體。
可以通過 NumField() 獲取所有結構體欄位的數目、進而遍歷,通過Field()方法獲取欄位的資訊。
type Student struct { Name string Sex int Age int Score float32 } func main() { //建立一個結構體變數 var s Student = Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } v := reflect.ValueOf(s) t := v.Type() kind := t.Kind() //分析s變數的型別,如果是結構體型別,那麼遍歷所有的欄位 switch kind { case reflect.Int64: fmt.Printf("s is int64n") case reflect.Float32: fmt.Printf("s is int64n") case reflect.Struct: fmt.Printf("s is structn") fmt.Printf("field num of s is %dn", v.NumField()) //NumFiled()獲取欄位數,v.Field(i)可以取得下標位置的欄位資訊,返回的是一個Value型別的值 for i := 0; i < v.NumField(); i++ { field := v.Field(i) //列印欄位的名稱、型別以及值 fmt.Printf("name:%s type:%v value:%vn", t.Field(i).Name, field.Type().Kind(), field.Interface()) } default: fmt.Printf("defaultn") } }
執行結果:
s is struct
field num of s is 4
name:Name type:string value:BigOrange
name:Sex type:int value:1
name:Age type:int value:10
name:Score type:float32 value:80.1
這裡需要說明幾個問題:
①列印欄位名稱的時候,使用的是t.Field(i).Name ,Name是靜態的,所以屬於型別的資訊
②列印值的時候,這裡將field.Interface()實際上相當於ValueOf的反操作(可以參考這篇文章https://www.jb51.net/article/255856.htm),所以才能把值列印出來
③此外如果Student中的Name欄位變為name(私有),那麼則會報錯,不能反射出私有變數 錯誤資訊 “panic: reflect.Value.Interface: cannot return value obtained from unexported field or method”
2.對結構體內的欄位進行賦值操作
參考下面的程式碼,對上面的Student進行賦值操作:
func main() { s := Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v n", s.Name, s.Sex, s.Age, s.Score) v := reflect.ValueOf(&s) //這裡傳的是地址!!! v.Elem().Field(0).SetString("ChangeName") v.Elem().FieldByName("Score").SetFloat(99.9) fmt.Printf("Name:%v, Sex:%v,Age:%v,Score:%v n", s.Name, s.Sex, s.Age, s.Score) }
結果:
Name:BigOrange, Sex:1,Age:10,Score:80.1
Name:ChangeName, Sex:1,Age:10,Score:99.9
3.獲取結構體裡面的方法
可以通過NumMethod()獲得接頭體裡面的方法數量,然後遍歷通過Method()獲取方法的具體資訊。如下程式碼所示:
//新增-設定名稱方法 func (s *Student) SetName(name string) { fmt.Printf("有引數方法 通過反射進行呼叫:%vn", s) s.Name = name } //新增-列印資訊方法 func (s *Student) PrintStudent() { fmt.Printf("無引數方法 通過反射進行呼叫:%vn", s) } func main() { s := Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } v := reflect.ValueOf(&s) //取得Type資訊 t := v.Type() fmt.Printf("struct student have %d methodsn", t.NumMethod()) for i := 0; i < t.NumMethod(); i++ { method := t.Method(i) fmt.Printf("struct %d method, name:%s type:%vn", i, method.Name, method.Type) } }
輸出:
struct student have 2 methods
struct 0 method, name:PrintStudent type:func(*main.Student)
struct 1 method, name:SetName type:func(*main.Student, string)
從結果中看到我們可以獲取方法的名稱以及簽名資訊,並且這個方法的輸出順序是按照字母排列的。
並且輸出結果可以看到一個有趣的現象:結構體的方法其實也是通過函數實現的例如 func(s Student) SetName(name string) 這個方法,反射之後的結果就是 func(main.Student , string) 實際上把Student當引數了。
此外還可以通過反射來呼叫這些方法。想要通過反射呼叫結構體裡面的方法,首先要知道方法呼叫時一個動態的,所以要先通過ValueOf獲取值,然後通過獲取的值進行方法的呼叫 ,通過 value裡面的Method方法 返回一個方法,然後通過Call方法呼叫,Call是引數是一個切片,也就是引數的列表。以下是Call方法的定義:可以看到引數是一個Value的陣列:
如下程式碼展示瞭如何呼叫有引數的方法和無引數的方法:
func main() { s := Student{ Name: "BigOrange", Sex: 1, Age: 10, Score: 80.1, } v := reflect.ValueOf(&s) //通過reflect.Value獲取對應的方法並呼叫 m1 := v.MethodByName("PrintStudent") var args []reflect.Value m1.Call(args) m2 := v.MethodByName("SetName") var args2 []reflect.Value name := "stu01" nameVal := reflect.ValueOf(name) args2 = append(args2, nameVal) m2.Call(args2) m1.Call(args) }
執行結果:
無引數方法 通過反射進行呼叫:&main.Student{Name:"BigOrange", Sex:1, Age:10, Score:80.1}
有引數方法 通過反射進行呼叫:&main.Student{Name:"BigOrange", Sex:1, Age:10, Score:80.1}
無引數方法 通過反射進行呼叫:&main.Student{Name:"stu01", Sex:1, Age:10, Score:80.1}
上面格式列印:
有時候我們在型別上面定義一些tag,例如使用json和資料庫的時候。Field()方法返回的StructField結構體中儲存著Tag資訊,並且Tag資訊可以通過一個Get(Key)的方法獲取出來,如下程式碼所示:
type Student struct { Name string `json:"jsName" db:"dbName"` } func main() { s := Student{ Name: "BigOrange", } v := reflect.ValueOf(&s) t := v.Type() field0 := t.Elem().Field(0) fmt.Printf("tag json=%sn", field0.Tag.Get("json")) fmt.Printf("tag db=%sn", field0.Tag.Get("db")) }
結果:
tag json=jsName
tag db=dbName
1.序列化和反序列化,比如json, protobuf等各種資料協定
2.各種資料庫的ORM,比如gorm,sqlx等資料庫中介軟體
3.組態檔解析相關的庫,比如yaml、ini等
到此這篇關於Go語言反射機制的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45