直接看例子:
type Myint inttype Person struct {}func main() { var myint Myint= 1 var person Person= Person{} s := 1 var intPtr =&s mySlice := []string{} myMap := map[string]int{} myintType := reflect.TypeOf(myint) personType := reflect.TypeOf(person) intPtrType := reflect.TypeOf(intPtr) mySliceType := reflect.TypeOf(mySlice) myMapType := reflect.TypeOf(myMap) myintKind := myintType.Kind() personKind := personType.Kind() intPtrKind := intPtrType.Kind() mySliceKind := mySliceType.Kind() myMapKind := myMapType.Kind() fmt.Printf("myint Type():%s,Kind():%s\n",myintType,myintKind) fmt.Printf("person Type():%s,personType,personKind) fmt.Printf("intPtr Type():%s,intPtrType,intPtrKind) fmt.Printf("mySlice Type():%s,mySliceType,mySliceKind) fmt.Printf("myMap Type():%s,myMapType,myMapKind) }
运行结果如下:
myint Type():main.Myint,Kind():intperson Type():main.Person,Kind():structintPtr Type():*int,Kind():ptrmySlice Type():[]string,Kind():slicemyMap Type():map[string]int,Kind():map
这里看出来Type是实际类型,Kind是底层类型。实际类型和底层类型是我给起的名字。比如type Myint int
的实际类型是Myint,底层类型是int。type Person struct {}
实际类型是Person,底层类型是struct。指向XXX
的指针,实际类型就是XXX
底层类型是指针。可以把实际类型理解成我们声明一个变量时候所用的类型,底层类型是golang提供的原始类型。
官方文档对Kind的解释是:
A Kind represents the specific kind of type that a Type represents.
翻译过来就是Kind是Type的类型。总之记住Kind比Type更加原始就好。
下面是go语言支持的所有Kind:
const ( InvalID Kind = iota 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)Elm方法
Type接口和Value接口都有Elem() 方法。
Type接口的Elem() 方法原型是Elem() Type
,注释:
// Elem returns a type‘s element type.
// It panics if the type‘s Kind is not Array,Chan,Map,Ptr,or Slice.
Elem返回的是调用者的元素的类型,如果调用者的Kind不是Array,Slice.那么会抛出异常。
看个Demo:
func main() {} s := 1 var intPtr =&s mySlice := []string{} myMap := map[string]int{} myArray:= [...]string{} var myChan chan int= make(chan int) intPtrKind := reflect.TypeOf(intPtr).Elem() mySliceKind := reflect.TypeOf(mySlice).Elem() myMapKind := reflect.TypeOf(myMap).Elem() myArrayKind := reflect.TypeOf(myArray).Elem() myChanKind := reflect.TypeOf(myChan).Elem() fmt.Printf("Elem():%s\n",intPtrKind) fmt.Printf("Elem():%s\n",mySliceKind) fmt.Printf("Elem():%s\n",myMapKind) fmt.Printf("Elem():%s\n",myArrayKind) fmt.Printf("Elem():%s\n",myChanElem)}/*打印结果intPtr Type():*int,Elem():intmySlice Type():[]string,Elem():stringmyMap Type():map[string]int,Elem():intmyArray Type():[0]string,Elem():stringmyChan Type():chan int,Elem():int*/
从上面可以看到:
对于指针类型来说Elem的结果是它指向的数据的类型
对于数组和切片类型来说Elem的结果是它存储的元素的类型
对于Map类型来说Elem的结果是它存储的Value的类型
对于通道类型来说Elem的结果是通道可以存储的数据的类型
Value接口的Elem() 方法原型是Elem() Value
,注释:
// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v‘s Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
调用方的的Kind必须是Interface或者指针,否则发生panic,Elem函数返回接口包含的值或者指针指向的值。
func main() { s := 1 var intPtr = &s intPtrValueElem := reflect.ValueOf(intPtr) fmt.Println("pointer Kind:",intPtrValueElem.Kind()," Value.Elem():",intPtrValueElem.Elem())}/*打印结果pointer Kind: ptr Value.Elem(): 1*/
从结果可以看出Value.Elem方法取出来的是指针指向的值。(这里只举了一个Kind是Ptr的例子,关于Kind是Interface的具体可以看看这里In Go,which value’s kind is reflect.Interface? )
如何设置值这里直接给几个例子
1.设置切片:
func main() { names := make([]string,3) val := reflect.ValueOf(names) for i:=0;i<val.Len();i++{ name := fmt.Sprintf("names%d",i) val.Index(i).SetString(name) } fmt.Println(val)}//运行结果[names0 names1 names2]
2.设置Map
func main() { names := make(map[string]int) val := reflect.ValueOf(names) val.SetMAPIndex(reflect.ValueOf("name1"),reflect.ValueOf(1)) //key=name1,value=name2 val.SetMAPIndex(reflect.ValueOf("name2"),reflect.ValueOf(2)) fmt.Println(val) //打印map[name1:1 name2:2] value1 :=val.MAPIndex(reflect.ValueOf("name1")) value2 :=val.MAPIndex(reflect.ValueOf("name2")) fmt.Println("value1:",value1," value2:",value2) //打印value1: 1 value2: 2}
3.修改通道类型的值
func main() { var myChan chan int= make(chan int,1) myChan <-1 val := reflect.ValueOf(myChan) fmt.Println(val.Recv()) //接收数据 val.Send(reflect.ValueOf(7)) fmt.Println(<-myChan) //发送数据}/*结果:1 true7*/
4.修改基本类型和struct类型
func main() { //int type a := 5 val := reflect.ValueOf(&a) if val.Elem().CanSet() { val.Elem().SetInt(10) } fmt.Println(a) //string s := "yuanjize" str := reflect.ValueOf(&s) if str.Elem().CanSet() { str.Elem().SetString("Tom") } fmt.Println(s) // struct user := &User{Username: "yuanjize",Passwd: "123",Age: 3} typ := reflect.TypeOf(user).Elem() value := reflect.ValueOf(user).Elem() //reflect.ValueOf(user)得到的Value打印出来是指针指向的地址,比如0xff998877。reflect.ValueOf(user).Elem() 得到的Value打印出来的是user的值,所我们后面要修改User的字段,所以这里要调用Elem方法。 for i := 0; i < typ.NumFIEld(); i++ { fIEldType := typ.FIEld(i) fIEldValue := value.FIEld(i) fmt.Printf("[BEFORE CHANGE] FIEldname:%s,FIEldValue:%v canSet:%t \n",fIEldType.name,fIEldValue.Interface(),fIEldValue.CanSet()) if fIEldValue.CanSet() { //这个字段是否可以修改 switch fIEldValue.Kind() { case reflect.Int: { fIEldValue.SetInt(10) } case reflect.String: { fIEldValue.SetString("dean") } default: fmt.Println("no such type") } } fmt.Printf("[AFTER CHANGE] FIEldname:%s,fIEldValue.CanSet()) }}
上面提供了几种反射golang数据类型的例子,可以发现对于map,slice和chan这几种引用类型来说我们在调用reflect.Value
函数的时候只需传入相应类型的变量就可以,但是对于基本类型和struct类型必须传入对应了类型的指针类型才可以在后面对值进行修改,否则在尝试修改值的时候会报panic。
在修改值之前可以先调用func (v Value) CanSet() bool
函数,他会告诉我们这个变量是否是可以修改的。
如果我们传入到reflect.Value
的类型是指针,那么我们要在获得reflect.Value
和reflect.Type
变量之后调用相应的Elem
方法才能获得指针指向的对象,这样才能对对象进行修改。
总结一下:要想通过反射改变类型的值,如果想要改变的是引用类型那么就直接传递引用类型,如果要改变的是非引用类型那么就需要传递该类型的指针,这个规则和通过函数调用修改函数变量的方法是一样的。
仿照Gin框架的bind部分写的一个练习Odm函数会把form参数中存放的字段一一映射到structPtr指向的结构体中。
/*使用例子:type Person struct{ name string `form:"name"` Age int `form:"age"`}person := &Person{}mMap :=map[string]string}{}mMap["name"]="yuanjize"mMap["age"] = "22"Odm(person,mMap)*/func Odm(structPtr interface{},form map[string][]string) { typ := reflect.TypeOf(structPtr) fmt.Println("------------------->:",typ) if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct{ panic("first Param must be a pointer of struct") } val := reflect.ValueOf(structPtr).Elem() typ = typ.Elem() for i:=0;i< val.NumFIEld();i++{ fIEldValue := val.FIEld(i) fIEldType := typ.FIEld(i) if !fIEldValue.CanSet(){ fmt.Printf("fIEld:%s can‘t be set",fIEldType.name) continue } fIEldKind := fIEldValue.Kind() if fIEldKind == reflect.Struct{ Odm(fIEldValue.Addr().Interface(),form) }else{ tag := fIEldType.Tag.Get("form") if tag == ""{ fmt.Printf("no such named:%s FIEld in Form \n",tag) continue } if fIEldKind == reflect.Slice{ //1.get type of ele 2.make s slice 3.fill slice length := len(form[tag]) if length <=0{ continue } itemType := fIEldType.Type.Elem().Kind() slice := reflect.MakeSlice(fIEldType.Type,length,length) for i,v:=range form[tag]{ setValue(itemType,slice.Index(i),v) } fIEldValue.Set(slice) }else{ //no slice or struct,find the fIEld and write if formVal,ok := form[tag];ok{ setValue(fIEldKind,fIEldValue,formVal[0]) }else{ fmt.Printf("no such named:%s FIEld in Form \n",tag) } } } }}func setValue(kind reflect.Kind,fIEld reflect.Value,value string){ switch kind { case reflect.Int:{ setInts(fIEld,value,0) } case reflect.Int8:{ setInts(fIEld,8) } case reflect.Int16:{ setInts(fIEld,16) } case reflect.Int32:{ setInts(fIEld,32) } case reflect.Int64:{ setInts(fIEld,64) } case reflect.Uint:{ setUints(fIEld,0) } case reflect.Uint8:{ setUints(fIEld,8) } case reflect.Uint16:{ setUints(fIEld,16) } case reflect.Uint32:{ setUints(fIEld,32) } case reflect.Uint64:{ setUints(fIEld,64) } case reflect.Bool:{ setBool(fIEld,value) } case reflect.String:{ setString(fIEld,value) } case reflect.float32:{ setfloat(fIEld,32) } case reflect.float64:{ setfloat(fIEld,64) } default: fmt.Println("undefine type :",kind) }}func setInts(fIEld reflect.Value,value string,bitSize int) { val,_ := strconv.ParseInt(value,10,bitSize) fIEld.SetInt(val)}func setUints(fIEld reflect.Value,_ := strconv.ParseUint(value,bitSize) fIEld.SetUint(val)}func setBool(fIEld reflect.Value,value string) { val,_ := strconv.ParseBool(value) fIEld.SetBool(val)}func setString(fIEld reflect.Value,value string) { fIEld.SetString(value)}func setfloat(fIEld reflect.Value,_ := strconv.Parsefloat(value,bitSize) fIEld.Setfloat(val)}
参考:
all_test.go 该文件是reflect包的测试文件,在源码中可以找到,测试文件中提供了很多API的用法。
The Laws of Reflection,Go Data Structures: Interfaces 可以看看这两篇文章,反射其实是对接口这个数据结构里面数据的读取和修改。
以上是内存溢出为你收集整理的golang 反射解惑全部内容,希望文章能够帮你解决golang 反射解惑所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)