[go 反射] 入门
首先认识go 反射的两大概念,反射之路少不了他们
- reflect.Type(接口)获取类型,和列名就找它
- reflect.Value(结构体)获取值,设置值找它
[tips] 通常是用这两者手底下的方法,reflect.Value结构体中有什么自行查看
演示
获取其类型(类型的准确识别定位是反射之路的基本功)
import (
"reflect"
"fmt"
)
type One struct{
One string `name:"one"`
Two int `age:"age"`
}
func main(){
var(
one One
two string
)
otp:=reflect.TypeOf(one)
ttp:=reflect.TypeOf(two)
fmt.Println(otp.Kind(),ttp.Kind())//Kind负责输出基本类型,int,string,map,array,slice,pointer,struct(这里的one就属于struct),func
//针对struct类获取其子字段名,特性,标签
if otp.Kind()==reflect.Struct{//类型的检验是防止意外panic的必要预防之一,减少代码的不可预测性
field_count:=otp.NumField()
if field_count>0{
var oftp reflect.StructField
for i:=0;i<field_count;i++{
oftp=otp.Field(i)
fmt.Println(oftp.Type.Kind(),oftp.Name,oftp.Tag)//走字段结构体输出才会拿到字段名和标签,reflect.Type拿到的名字是Kind()的go基本类型
}
}
}
}
Elem()是reflect.Type和reflect.Value都存在的方法,它的作用为获取指针指向的本体,如果非指针使用此方法会造成panic
通过反射设置值
import (
"fmt"
"reflect"
)
func main(){
//演示效果:将two的值设置到one上
var(
one string
two string="im two"
)
otp:=reflect.TypeOf(one)
ttp:=reflect.TypeOf(two)
//假设我们不知道传入的两个参数的类型
if otp==ttp{//适用于一切严格类型对比(非严格就是,你希望两个类型之间出现兼容性,特别是不同结构体之间的兼容,需要手动对比和赋值)
ovl:=reflect.ValueOf(&one).Elem()//这里为了能使其修改必须是其指针,elem()导向本体过后便可对其操作
tvl:=reflect.ValueOf(two)//单纯获得其值,并且这里不是指向two的指针,所以是不可修改的
if ovl.CanSet(){//这个需为true,才能进行后续赋值,不检查这个,如果是你自己结构体字段首字母小写之类的导致不能赋值会造成panic
ovl.Set(tvl)//赋值
fmt.Println(one)
}
}
}
使用泛型进行限制
虽然两个参数都是类型不作限制,但是dst会被限制为任意类型的指针。这在[go 反射]之旅中能在输入参数上进行很大程度的优化,例如后续的反射拷贝(因为dst必须是指向你拷贝目标的指针才能对目标进行更改,而强制dst为指针也是变相提醒使用此函数的人,此处必须为指针
)
func One[T any](dst *T,src any)error
一些可能会踩进去的小坑
- 反射未初始化的指针(
var one *string
,直接进行反射赋值会导致panic:invaild memory);解决方案:reflect.Value类型有一个方法IsNil,通过它先预判一遍,空的就返回,或者反射创建对象(后续go 反射 进阶会细说) - 结构体字段首字母大小写:如果你结构体中某个字段不能canset,并且你valueof目标结构体的指针再elem进行canset,第一反应应该是检查大小写(大小写也是流畅控制是否暴露内部字段的重要技巧)
现在你就学会了go 反射的基础技能了,能使用反射识别类型,和赋值。如果操练此文时遇见什么问题google也无解的,可私信我,一起学习交流交流