免费建站有哪些网站,wordpress首页幻灯片尺寸,东营招标信息网官网首页,铝木门窗网站建设今天主要来聊聊 Go 语言中反射#xff0c;希望对你有新的认知
虽然很多人使用 Go 语言有一定时间了#xff0c;甚至有的使用了 1 年 2 年#xff0c;然后对于 Go 语言中的反射还是模棱两可#xff0c;使用起来的时候#xff0c;心里也不是非常有底气
更有甚者#xff0…今天主要来聊聊 Go 语言中反射希望对你有新的认知
虽然很多人使用 Go 语言有一定时间了甚至有的使用了 1 年 2 年然后对于 Go 语言中的反射还是模棱两可使用起来的时候心里也不是非常有底气
更有甚者几乎不使用反射当然也不是什么错在工作中能用最简单最高效又可扩展性能还好的方式来进行处理自然是最 nice 没有必要去生搬硬套一些高级用法毕竟工作不是我们的试炼场可以自己下来多多实验本次就来好好看看如何去玩反射
文章分别从如下五个方面来聊
反射是什么 反射的规则 使用案例并灵活运用 反射原理 总结
简单来看反射是什么
简单来看反射就是在程序运行时期对程序本身进行访问和修改的能力例如在程序运行时可以修改程序的字段名称字段值还可以给程序提供接口访问的信息等等
这是 Go 语言中提供的一种机制我们可以在 Go 语言公共库中可以看到很多关于 reflect 的使用位置
例如常用的 fmt 包常用的 json 序列化和反序列化自然前面我们说到的 gorm 库自然也是使用了反射的 可是我们一般为什么要使用反射呢
根据反射的能力自然是因为我们提供的接口并不知道传入的数据类型会是什么样的 只有当程序运行的时候才知道具体的数据类型
但是我们编码的时候又期望去校验程序运行时传入的类型会是什么样的例如 json 的序列化并对其这种具体的数据进行操作这个时候咱们就需要用到反射的能力了
所以对于使用到反射的地方你都能看到 interface{} 是不是就不奇怪了呢
正是因为不确定传入的数据类型会是什么样的所以才设计成 interface{} 那么如果对于 interface 有还不清楚他的特点和使用方式的可以查看历史文章
关于 interface{} 会有啥注意事项上 关于 interface{} 会有啥注意事项下
先关注反射的规则
首先关注反射的三个重要的定律知道规则之后我们按照规则玩就不会有什么问题只有当我们不清楚规则总是触发条款的时候才会出现奇奇怪怪的问题
反射是可以将 接口类型的变量 转换成 反射类型的对象 反射可以将 反射类型的对象 转换成 接口类型的变量 我们在运行时要去修改的 反射类型的对象 那么要求这个对象对应的值是要可写的
对于上述 3 个规则也是比较好理解还记的之前我们说过的 unsafe 包里面的指针吗
都是将我们常用的数据类型转换成包例如 unsafe包或者 reflect 包里面的指定数据类型然后再按照包里面的规则进行修改数据
相当于换个马甲就可以进行不同的操作了
关注使用案例并灵活运用
一般咱们先会基本的应用再去研究他的原理研究他为什么可以这样用慢慢的才能理解的更加深刻
对于定律一将 接口类型的变量 转换成 反射类型的对象
实际上此处说的 接口类型的变量 我们可以传入任意数据类型的变量例如 int, float, string ,map, slice, struct 等等
反射类型的对象 这里就可以理解成 reflect 反射包中的 reflect.Type 和 reflect.Value 对象可以通过 reflect 包中提供的 TypeOf 和 ValueOf 函数得到
其中 reflect.Type 实际上是一个 interface 他里面包含了各种接口需要进行实现它里面提供了关于类型相关的信息
其中如下图可以查看到 reflect.Type 的所有方法其中
绿色的 是所有数据类型都是可以调用的 红色的是 函数类型数据可以调用的 黑色的是 Map数组 Array通道 Chan指针 Ptr 或者 切片Slice 可以调用的 蓝色的是结构体调用的 黄色的是通道 channel 类型调用的 reflect.Value 实际上是一个 struct根据这个 struct 还关联了一组方法这里面存放了数据类型和具体的数据通过查看其数据结构就可以看出
type Value struct {typ *rtypeptr unsafe.Pointerflag
}看到此处的 unsafe.Pointer 是不是很熟悉底层自然就可以将 unsafe.Pointer 转换成 uintptr然后再修改其数据后再转换回来对于 Go 指针不太熟悉的可以查看这篇文章
GO 中的指针
写一个简单的 demo 就可以简单的获取到变量的数据类型和值
func main() {var demoStr string now reflectfmt.Println(type:, reflect.TypeOf(demoStr))fmt.Println(value:, reflect.ValueOf(demoStr))
}对于定律二将 反射类型的对象 转换成 接口类型的变量
我们可以通过将 reflect.Value 类型转换成我们具体的数据类型因为 reflect.Value 中有对应的 typ *rtype 以及 ptr unsafe.Pointer
例如我们可以 通过 reflect.Value 对象的 interface() 方法来处理 func main() {var demoStr string now reflectfmt.Println(type:, reflect.TypeOf(demoStr))fmt.Println(value:, reflect.ValueOf(demoStr))var res stringres reflect.ValueOf(demoStr).Interface().(string)fmt.Println(res ,res)
}对于定律三修改反射类型的对象
首先我们看上书的 demo 代码传入 TypeOf 和 ValueOf 的变量实际上也是一个拷贝那么如果期望在反射类型的对象中修改其值那么就需要拿到具体变量的地址然后再进行修改前提是这个变量是可写的
举个例子你就能明白
func main() {var demoStr string now reflectv : reflect.ValueOf(demoStr)fmt.Println(is canset , v.CanSet())//v.SetString(hello world) // 会panic}可以先调用 reflect.Value 对象的 CanSet 查看是否可写如果是可写的我们再写如果不可写就不要写了否则会 panic 那么传入变量的地址就可以修改了 传入地址的思路没有毛病但是我们去设置值的方式有问题因此也会出现上述的 panic 情况
此处仔细看能够明白反射的对象 v 自然是不可修改的我们应该找到 reflect.Value 里面具体具体的数据指针那么才是可以修改的可以使用 reflect.Value 的 Elem 方法 稍微复杂一点的
看上了上述案例可能会觉得那么简单的案例一演示就 ok但是工作中一用就崩溃那自然还是没有融会贯通说明还没有消化好再来一个工作中的例子
一个结构体里面有 mapmap 中的 key 是 stringvalue 是 []string 需求是访问 结构体中 hobby 字段对应的 map key 为 sport 的切片的第1 个元素并将其修改为 hellolworld type RDemo struct {Name stringAge intMoney float32Hobby map[string][]string
}func main() {tmp : RDemo{Name: xiaomiong,Age: 18,Money: 25.6,Hobby: map[string][]string{sport: {basketball, football},food: {beef},},}v : reflect.ValueOf(tmp).Elem() // 拿到结构体对象h : v.FieldByName(Hobby) // 拿到 Hobby 对象h1 : h.MapKeys()[0] // 拿到 Hobby 的第 0 个keyfmt.Println(key1 name ,h1.Interface().(string))sli : h.MapIndex(h1) // 拿到 Hobby 的第 0 个key对应的对象str : sli.Index(1) // 拿到切片的第 1 个对象fmt.Println(str.CanSet())str.SetString(helloworld)fmt.Println(tmp ,tmp)
}可以看到上述案例运行之后有时可以运行成功有时会出现 panic 的情况相信细心的 xdm 就可以看出来是因为 map 中的 key 是 无序的导致的此处也提醒一波使用 map 的时候要注意这一点
看上述代码是不是就能够明白咱们使用反射去找到对应的数据类型然后按照数据类型进行处理数据的过程了呢
有需要的话可以慢慢的去熟练反射包中涉及的函数重点是要了解其三个规则对象转换方式访问方式以及数据修改方式
反射原理
那么通过上述案例可以知道关于反射中数据类型和数据指针对应的值是相当重要的不同的数据类型能够用哪些函数这个需要注意否则用错直接就会 panic
TypeOf
来看 TypeOf 的接口中涉及的数据结构 在 reflect 包中 rtype 是非常重要的Go 中所有的类型都会包含这个结构所以咱们反射可以应用起来结构如下
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size uintptrptrdata uintptrhash uint32 tflag tflagalign uint8fieldAlign uint8kind uint8equal func(unsafe.Pointer, unsafe.Pointer) boolgcdata *byte str nameOffptrToThis typeOff
}其中可以看到此处的 rtype 的结构保持和 runtime/type.go 一致 都是关于数据类型的表示以及对应的指针关于这一块的说明和演示可以查看文末的 interface{} 处的内容
ValueOf
从 ValueOf 的源码中我们可以看到重要的是 emptyInterface 结构 // emptyInterface is the header for an interface{} value.
type emptyInterface struct {typ *rtypeword unsafe.Pointer
}emptyInterface 结构中有 rtype 类型的指针 word 自然是对应的数据的地址了
reflect.Value 对象中的方法也是非常的多用起来和上述说到的 reflect.Type 接口中的功能类似
关于源码中涉及到的方法就不再过多的赘述了更多的还是需要自己多多实践才能体会的更好
殊不知此处的 reflect.Value 也是可以转换成 reflect.Type 可以查看源码中 reflect\value.go 的 func (v Value) Type() Type {
其中 reflect.Value reflect.Type 和任意数据类型 可以相互这样来转换
如下图 总结
至此关于反射就聊到这里一些关于源码的细节并没有详细说更多的站在一个使用者的角度去看反射需要注意的点
关于反射大多的人是建议少用因为是会影响到性能不过如果不太关注这一点那么用起来还是非常方便的
高级功能自然也是双刃剑你用不好就会 panic如果你期望去使用他那么就去更多的深入了解和一步一步的吃透他吧
大道至简反射三定律活学活用
感谢阅读欢迎交流点个赞关注一波 再走吧
欢迎点赞关注收藏
朋友们你的支持和鼓励是我坚持分享提高质量的动力 好了本次就到这里
技术是开放的我们的心态更应是开放的。拥抱变化向阳而生努力向前行。
我是阿兵云原生欢迎点赞关注收藏下次见~
文中提到的技术点感兴趣的可以查看这些文章
Go 语言中 panic 和 recover 搭配使用 关于 interface{} 会有啥注意事项上 关于 interface{} 会有啥注意事项下 GO 中的指针 你真的知道 GO 中 nil 代表什么吗
可以进入地址进行体验和学习https://xxetb.xet.tech/s/3lucCI