💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ### 反射是什么 反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。 ### 反射的三大定律 * 反射可以将“接口类型变量”转换为“反射类型对象”; * 反射可以将“反射类型对象”转换为“接口类型变量”; * 如果要修改“反射类型对象”,其值必须是“可写的”(settable)。 ### 用法 #### reflect.TypeOf() ``` func TypeOf(i interface{}) Type ``` 在反射中关于类型还划分为两种:`类型(Type)`和`种类(Kind)` `类型(Type)`:就是自己定义的类型 `种类(Kind)`:指底层的类型 ~~~ type newInt int func main() { var testNum newInt = 10 typeOfTestNum := reflect.TypeOf(testNum) fmt.Println(typeOfTestNum) //main.newInt //获取类型 fmt.Println(typeOfTestNum.Name()) //newInt //获取种类,即底层数据类型 fmt.Println(typeOfTestNum.Kind()) //int fmt.Println(reflect.ValueOf(testNum)) //10 } ~~~ Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的`.Name()`都是返回`空`。 #### reflect.ValueOf() ``` func ValueOf(i interface{}) Value ``` 使用reflect.ValueOf() 获取的值的类型是`reflect.Value`类型,其中包含了原始值的值信息。 可以进行转化 | 方法 | 说明 | | --- | --- | | Interface() interface {} | 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型 | | Int() int64 | 将值以 int 类型返回,所有有符号整型均可以此方式返回 | | Uint() uint64 | 将值以 uint 类型返回,所有无符号整型均可以此方式返回 | | Float() float64 | 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回 | | Bool() bool | 将值以 bool 类型返回 | | Bytes() \[\]bytes | 将值以字节数组 \[\]bytes 类型返回 | | String() string | 将值以字符串类型返回 | ~~~go func reflectValue(x interface{}) { v := reflect.ValueOf(x) k := v.Kind() switch k { case reflect.Int64: // v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换 fmt.Printf("type is int64, value is %d\n", int64(v.Int())) case reflect.Float32: // v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换 fmt.Printf("type is float32, value is %f\n", float32(v.Float())) case reflect.Float64: // v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换 fmt.Printf("type is float64, value is %f\n", float64(v.Float())) } } func main() { var a float32 = 3.14 var b int64 = 100 reflectValue(a) // type is float32, value is 3.140000 reflectValue(b) // type is int64, value is 100 // 将int类型的原始值转换为reflect.Value类型 c := reflect.ValueOf(10) fmt.Printf("type c :%T\n", c) // type c :reflect.Value } ~~~ ### isNil()和isValid() #### isNil() 常被用于判断指针是否为空 ~~~go func (v Value) IsNil() bool ~~~ `IsNil()`报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。 #### isValid() 常被用于判定返回值是否有效 ~~~go func (v Value) IsValid() bool ~~~ `IsValid()`返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。 ~~~ func main() { var testNum int = 10 ptrOfTestNum := &testNum valueOfTestNum := reflect.ValueOf(ptrOfTestNum) fmt.Println(valueOfTestNum.IsNil()) // false fmt.Println(valueOfTestNum.IsValid()) //true } ~~~ #### 修改反射值 使用反射进行值属性获取或修改值前,**一定要确保操作对象是可寻址的**。对于修改值得操作,还要**确保操作对象是可被修改的**。Go 语言提供了相应的方法帮助我们进行判断,分别是 CanAddr() 和 CanSet(); ~~~ func main() { var testNum int = 10 ptrOfTestNum := &testNum valueOfPtrTestNum := reflect.ValueOf(ptrOfTestNum) fmt.Println(valueOfPtrTestNum.Elem().CanAddr()) fmt.Println(valueOfPtrTestNum.Elem().CanSet()) valueOfPtrTestNum.Elem().SetInt(20) } ~~~ **Elem() 函数的作用是返回指针指向的数据** 注意:使用 Elem() 函数时,若作用于非指针或接口时,将引发宕机。作用于空指针时,将返回 nil。 ~~~ func (v Value) Elem() Value { k := v.kind() switch k { case Interface: var eface interface{} if v.typ.NumMethod() == 0 { eface = *(*interface{})(v.ptr) } else { eface = (interface{})(*(*interface { M() })(v.ptr)) } x := unpackEface(eface) if x.flag != 0 { x.flag |= v.flag.ro() } return x case Ptr: ptr := v.ptr if v.flag&flagIndir != 0 { ptr = *(*unsafe.Pointer)(ptr) } // The returned value's address is v's value. if ptr == nil { return Value{} } tt := (*ptrType)(unsafe.Pointer(v.typ)) typ := tt.elem fl := v.flag&flagRO | flagIndir | flagAddr fl |= flag(typ.Kind()) return Value{typ, ptr, fl} } panic(&ValueError{"reflect.Value.Elem", v.kind()}) } ~~~ ### 结构体反射 `reflect.Type`中与获取结构体成员相关的的方法如下表所示。 | 方法 | 说明 | | --- | --- | | Field(i int) StructField | 根据索引,返回索引对应的结构体字段的信息。 | | NumField() int | 返回结构体成员字段数量。 | | FieldByName(name string) (StructField, bool) | 根据给定字符串返回字符串对应的结构体字段的信息。 | | FieldByIndex(index \[\]int) StructField | 多层成员访问时,根据 \[\]int 提供的每个结构体的字段索引,返回字段的信息。 | | FieldByNameFunc(match func(string) bool) (StructField,bool) | 根据传入的匹配函数匹配需要的字段。 | | NumMethod() int | 返回该类型的方法集中方法的数目 | | Method(int) Method | 返回该类型方法集中的第i个方法 | | MethodByName(string)(Method, bool) | 根据方法名返回该类型方法集中的方法 | ~~~ rt := reflect.TypeOf(&Teacher{}) fmt.Println(rt.NumMethod(), rt.Method(0).Name) for i := 0; i < rt.NumField(); i++ { s := rt.Field(i) fmt.Println(s, s.Name, s.Type, s.Tag)//获取所有的tag,获取单个tag,Tag.Get(“json”) } rt1 := reflect.ValueOf(T1{Name: "hahha", Age: 55}) for i := 0; i < rt1.NumField(); i++ { //s1 := rt.Field(i) s := rt1.Field(i) //fmt.Println(s1.Name, s) fmt.Println(s) //hahha 55 } ~~~ ### 使用反射调用函数 ~~~ func addCalc(num1 int, num2 int) int { return num1 + num2 } func main() { ret := reflect.TypeOf(addCalc) fmt.Println(ret) //func(int, int) int ret1 := reflect.ValueOf(addCalc) fmt.Println(ret1) //0xe39da0 //用call调用,并传参,参数类型是reflect.Value res := ret1.Call([]reflect.Value{reflect.ValueOf(10), reflect.ValueOf(10)}) fmt.Println(res[0]) } ~~~ ### 使用反射创建实例 通过反射创建实例,通常**用于创建一个与已知变量同类型的变量**。如此创建的**变量类型只有在程序运行时才会被确定,更加灵活多变**。 举例来说,现有一个变量 num,它的类型是自定义的 myInt 类型。我们若想创建与其相同类型的变量,方法如下: ~~~go type myInt int func main() { var num myInt = 100 typeOfNum := reflect.TypeOf(num) anotherNum := reflect.New(typeOfNum) anotherNum.Elem().SetInt(300) fmt.Println(num) fmt.Println(anotherNum.Type(), anotherNum.Type().Kind()) fmt.Println(anotherNum.Elem().Int()) } ~~~ 使用反射创建变量,核心在于**reflect.New() 函数。该函数接收 reflect.Type 类型参数,返回 reflect.Value 类型值**。该值是一个指针,本例中的 anotherNum 类型实际上是 \*main.myInt,从种类上讲是 ptr。 运行这段代码,控制台输出如下: > 100 > *main.myInt ptr > 300