[go 反射] 结构体
本文你将了解
- 不同类型结构体直接通过反射拷贝
- 通过反射调用结构体方法
- 常踩的坑点
不同类型结构体直接通过反射拷贝
普通的拷贝
type One struct{
Name string
Age int
}
var one One=One{"jim",20}
var one_copy One=one//拷贝完成
反射拷贝
type One struct{
Name string
Age int
}
type Two struct{
One string
Two int
}
type Three struct{
One string
Two string
Three int
Four bool
}
type str_error string//自制简易错误类型
func (s str_error)Error()string{
return str_error(s)
}
var one One=One{"jim",20}
var one_copy Two
var one_copyt Three
func main(){
Struct_Copy_Strict(&one_copy,one)//下文实现的严格拷贝
Struct_Copy(&one_copy,one)//下文实现带兼容性的拷贝
}
func Struct_Copy[T any](dst *T, src any) (err error) {
//这里示范为了将代码精简到最简,把很多判断条件都省略了。正常使用,指针判断,类型判断,空值判断都是必不可少的!!!
dtp, stp := reflect.TypeOf(dst).Elem(), reflect.TypeOf(src)
dvl, svl := reflect.ValueOf(dst).Elem(), reflect.ValueOf(src)
var i, j = 0, 0
var dftp, sftp reflect.Type
var dfvl reflect.Value
var setpos []int = make([]int, 0, stp.NumField())
for i < dtp.NumField() && j < stp.NumField() {//预确定是否能进行拷贝,确保拷贝的完整性,不会拷贝到一半不能拷贝退出,造成dst污染
dftp, sftp = dtp.Field(i).Type, stp.Field(j).Type
dfvl = dvl.Field(i)
if dftp == sftp && dfvl.CanSet() {
setpos = append(setpos, i)
}
i++
}
if len(setpos) < stp.NumField() {
err = str_error("copy struct failed")
return
}
for i = 0; i < len(setpos); i++ {
dvl.Field(setpos[i]).Set(svl.Field(i))
}
return err
}
func Struct_Copy_Strict[T any](dst *T, src any) (err error) {
dtp, stp := reflect.TypeOf(dst).Elem(), reflect.TypeOf(src)
dvl, svl := reflect.ValueOf(dst).Elem(), reflect.ValueOf(src)
if dtp.NumField() == stp.NumField() {
if dtp.NumField() > 0 {
for i := 0; i < dtp.NumField(); i++ {
if dtp.Field(i).Type != stp.Field(i).Type && dvl.Field(i).CanSet() {
err = str_error("can't copy two struct, because them are different")
return
}
}
for i := 0; i < dtp.NumField(); i++ {
dvl.Field(i).Set(svl.Field(i))
}
}
} else {
err = str_error("can't copy two struct, because them are different")
}
return
}
反射调用结构体方法
结构体方法调用反射主要为反射调用函数,如有不清楚请查看 [go 反射] 函数
关键方法
NumMethod()
reflect.Type接口和reflect.Value都有,查看结构体下方法数量Method(int).Func
得到一个reflect.Value类型,使用和正常反射使用函数一样。有一点需注意,参数会多一个指针参数,但是此参数传参时请跳过
import (
"reflect"
"fmt"
)
type One struct{
}
func (s One)Greet()string{
return "hello golang"
}
func (s *One)Greet2()string{
return "hello golang too"
}
func main(){
var one One
tp:=reflect.TypeOf(one)//能识别并调用Greet
tpp:=reflect.TypeOf(&one)//能识别并调用Greet2
fmt.Println(tp.NumMethod(),tpp.NumMethod())//1,1
ans:=tp.Method(0).Func.Call([]reflect.Value{})
fmt.Println(ans[0].String())
ans=tpp.Method(0).Func.Call([]refelct.Value{})
}
常踩的坑
- 方法首字母大小写问题,首字母小写反射会搜不到方法
- 方法传参,自行通过NumIn时得到的结果会比实际需要的参数多一个,所以需要从1号位为起点开始遍历传入参数