首页 > 其他分享 >go reflect

go reflect

时间:2024-04-08 14:35:01浏览次数:22  
标签:反射 fmt Elem Value reflect Println go

go reflect

反射核心

反射的核心是两个对象,分别是 reflect.Type 和 reflect.Value。 它们分别代表了 go 语言中的类型和值。我们可以通过 reflect.TypeOf 和 reflect.ValueOf 来获取到一个变量的类型和值。

func main() {
	var a = 1
	fmt.Println(reflect.ValueOf(a))
	fmt.Println(reflect.TypeOf(a))
}

反射定律

三条反射定律:

  • 反射可以将 interface 类型变量转换成反射对象。
  • 反射可以将反射对象还原成 interface 对象。
  • 如果要修改反射对象,那么反射对象必须是可设置的(CanSet)。

反射可以将 interface 类型变量转换成反射对象

其实也就是上面的 reflect.Type 和 reflect.Value,我们可以通过 reflect.TypeOf 和 reflect.ValueOf 来获取到一个变量的反射类型和反射值。

反射可以将反射对象还原成 interface 对象

我们可以通过 reflect.Value.Interface 来获取到反射对象的 interface 对象,也就是传递给 reflect.ValueOf 的那个变量本身。 不过返回值类型是 interface{},所以我们需要进行类型断言。

func main() {
	var a = 1
	i := reflect.ValueOf(a).Interface()
	fmt.Println(i.(int))
}

如果要修改反射对象,那么反射对象必须是可设置的(CanSet)

我们可以通过 reflect.Value.CanSet 来判断一个反射对象是否是可设置的。如果是可设置的,我们就可以通过 reflect.Value.Set 来修改反射对象的值。
那什么情况下一个反射对象是可设置的呢?前提是这个反射对象是一个指针,然后这个指针指向的是一个可设置的变量。在我们传递一个值给 reflect.ValueOf 的时候,reflect.ValueOf 实际上会返回该值的一个副本(包括指针),当我们对副本进行修改的时候,实际上对原数据没有任何影响。所以 go 语言在这里做了一个限制,如果我们传递进 reflect.ValueOf 的变量是一个普通的变量,那么在我们设置反射对象的值的时候,就会报错。

func main() {
	var a = 1
	p := &a
	v := reflect.ValueOf(p).Elem()
	fmt.Println(v)  // 1
	fmt.Println(*p) // 1
	var b = 2
	p = &b
	fmt.Println(v) // 1
	fmt.Println(*p) // 2
}

并且,当传入的值是一个指针时,i2.CanSet() 的返回值依旧是 false,这是因为如果我们直接修改指针的值是没有任何意义的,因为我们如果修改了指针变量,也就相当于将该指针指向了另外的地址,这也是没有任何意义的。所以,我们需要调用 Elem 方法,获取指针实际指向的值。

func main() {
	var a = 1
	i1 := reflect.ValueOf(a)
	fmt.Println(i1.CanSet()) // false
	i2 := reflect.ValueOf(&a)
	fmt.Println(i2.CanSet()) // false
	fmt.Println(i2.Elem().CanSet()) // true
}

Elem 方法

reflect.value 的 Elem 方法

reflect.Value 的 Elem 方法的作用是获取指针指向的值,或者获取接口的动态值。也就是说,能调用 Elem 方法的反射对象,必须是一个指针或者一个接口()。
在使用其他类型的 reflect.Value 来调用 Elem 方法的时候,会 panic

type MyStruct struct {
	name string
}

func main() {
	ms := MyStruct{name: "test"}
	v1 := reflect.ValueOf(&ms)
	v2 := reflect.ValueOf(ms)
	fmt.Println(v1.Elem().CanSet()) // true
	fmt.Println(v2.Elem().CanSet()) // panic: reflect: call of reflect.Value.Elem on struct Value
}

对于指针,则作用类似于解指针。对于接口,因为接口中包含了类型和数据,所以 Elem 则是返回接口的数据部分。

reflect.Type 的 Elem 方法

reflect.Type 的 Elem 方法的作用是获取数组、chan、map、指针、切片关联元素的类型信息,也就是说,对于 reflect.Type 来说,能调用 Elem 方法的反射对象,必须是数组、chan、map、指针、切片中的一种,其他类型的 reflect.Type 调用 Elem 方法会 panic。需要注意的是,如果我们要获取 map 类型 key 的类型信息,需要使用 Key 方法,而不是 Elem 方法。

func main() {
	var a = []int{1, 3, 4}
	m := make(map[int]string)
	fmt.Println(reflect.TypeOf(a).Elem()) // int
	fmt.Println(reflect.TypeOf(m).Elem()) // map直接使用 Elem 返回的是值的类型
	fmt.Println(reflect.TypeOf(m).Key())  // 获取键的类型需要使用 key 方法
}

interface 方法

该函数接收一个 Value 类型的参数 v,并返回一个 interface{} 类型的值。该函数的作用是将 v 转换为一个 interface{} 类型的值。需要注意的是,返回值只包括值部分,并不包含类型部分,使用想要使用还需要进行类型断言,转换为具体的值。

func main() {
	var i = 1
	var a interface{} = &i
	b := reflect.ValueOf(a).Interface()
	fmt.Println(b == a) // true
	d := reflect.ValueOf(i).Interface() // d == (interface{} = 1)
}

Kind

在 go 中,我们可以自定义各种各样的类型。

type MyType1 int
type MyType2 float32

但是在底层,我们定义的类型都是 go 的基本类型之一。这个基本类型就是 Kind

type Kind uint

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
	Pointer
	Slice
	String
	Struct
	UnsafePointer
)

所以,我们可以通过有限的 reflect.Type 的 Kind 来进行类型判断。枚举出可能出现的类型。

func main() {
	var a = 1
	switch reflect.ValueOf(a).Kind() {
	case reflect.Int:
		fmt.Println("yes")
	}
}

addressable

在 go 的反射系统中有两个关于寻址的方法:CanAddr 和 CanSet。
CanAddr 方法的作用是判断反射对象是否可以寻址,也就是说,如果 CanAddr 返回 true,那么我们就可以通过 Addr 方法来获取反射对象的地址。如果 CanAddr 返回 false,那么我们就不能通过 Addr 方法来获取反射对象的地址。对于这种情况,我们就无法通过反射对象来修改变量的值。
但是,CanAddr 是 true 并不是说 reflect.Value 一定就能修改变量的值了。reflect.Value 还有一个方法 CanSet,只有 CanSet 返回 true,我们才能通过反射对象来修改变量的值。

func main() {
	var a = 1
	fmt.Println(reflect.ValueOf(&a).Elem().CanAddr()) // true
	fmt.Println(reflect.ValueOf(&a).CanAddr()) // false
	fmt.Println(reflect.ValueOf(a).CanAddr()) // false
}

总结

  • reflect 包提供了反射机制,可以在运行时获取变量的类型信息、值信息、方法信息等等。
  • go 中的 interface{} 实际上包含了两个指针,一个指向类型信息,一个指向值信息。正因如此,我们可以在运行时通过 interface{} 来获取变量的类型信息、值信息。
  • reflect.Type 代表一个类型,reflect.Value 代表一个值。通过 reflect.Type 可以获取类型信息,通过 reflect.Value 可以获取值信息。
  • 反射三定律:
    • 反射可以将 interface 类型变量转换成反射对象。
    • 反射可以将反射对象还原成 interface 对象。
    • 如果要修改反射对象,那么反射对象必须是可设置的(CanSet)。
  • reflect.Value 和 reflect.Type 里面都有 Elem 方法,但是它们的作用不一样:
    • reflect.Type 的 Elem 方法返回的是元素类型,只适用于 array、chan、map、pointer 和 slice 类型的 reflect.Type。
    • reflect.Value 的 Elem 方法返回的是值,只适用于接口或指针类型的 reflect.Value。
  • 通过 reflect.Value 的 Interface 方法可以获取到反射对象的原始变量,但是是 interface{} 类型的。
  • Type 和 Kind 都表示类型,但是 Type 是类型的反射对象,Kind 是 go 类型系统中最基本的一些类型,比如 int、string、struct 等等。
  • 如果我们想通过 reflect.Value 来修改变量的值,那么 reflect.Value 必须是可设置的(CanSet)。同时如果想要 CanSet 为 true,那么我们的变量必须是可寻址的。
  • 我们有很多方法可以创建 reflect.Type 和 reflect.Value,我们需要根据具体的场景来选择合适的方法。
  • reflect.Type 和 reflect.Value 里面,都有一部分方法是通用的,也有一部分只适用于特定的类型。如果我们想要调用那些适用于特定类型的方法,那么我们必须先判断 reflect.Type 或 reflect.Value 的类型(这里说的是 Kind),然后再调用。

参考文章:深入理解 go reflect - 反射基本原理

标签:反射,fmt,Elem,Value,reflect,Println,go
From: https://www.cnblogs.com/NorthnightX/p/18120481

相关文章

  • python计算机毕设【附源码】基于Android开发的智能音乐播放系统(django+mysql+论文)
    本系统(程序+源码)带文档lw万字以上  文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:随着移动互联网的飞速发展,智能手机已经成为人们日常生活中不可或缺的一部分。在众多手机应用中,音乐播放系统是用户使用频率较高的应用之一。传统的音乐播放......
  • How to change Google Chrome DevTools codes highlight theme color All In One
    HowtochangeGoogleChromeDevToolscodeshighlightthemecolorAllInOne如何更改GoogleChromeDevTools代码高亮主题颜色demosLightPink(......
  • Django框架之序列化组件
    一、为什么要序列化呢?我们在写一些项目前后端是分离的,这意味着无法直接利用django提供的模版语法来实现前后端的数据交互,需要将数据转换成前后端都能接收处理的格式,即json,一般的格式都是列表套字典。那么我的前端想拿到由ORM得到的数据库里面的一个个用户对象,而我的后端也想直接......
  • Django框架之批量插入数据
    一、项目需求浏览器中访问django后端某一条url(如:127.0.0.1:8080/index/)实时朝数据库中生成一千条数据并将生成的数据查询出来并展示到前端页面二、数据准备创建模型表classBook(models.Model):title=models.CharField(max_length=32)三、单条插入数据1......
  • 蓝桥杯练习系统(算法训练)ALGO-963 转圈游戏
    资源限制内存限制:128.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s问题描述n个小伙伴(编号从0到n-1)围坐一圈玩游戏。按照顺时针方向给n个位置编号,从0到n-1。最初,第0号小伙伴在第0号位置,第1号小伙伴在第1号位置,……,依此类推。游戏规......
  • 蓝桥杯练习系统(算法训练)ALGO-962 积木大赛
    资源限制内存限制:128.0MB C/C++时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s问题描述THU幼儿园举办了一年一度的“积木大赛”。今年比赛的内容是搭建一座宽度为n的大厦,大厦可以看成由n块宽度为1的积木组成,第i块积木的最终高度需要是hi。在搭建开始......
  • 基于项目的协同过滤推荐算法(Item-Based Collaborative Filtering Recommendation Alg
    前言协同过滤推荐系统,包括基于用户的、基于项目的息肉通过率等,今天我们读一篇基于项目的协同过滤算法的论文。今天读的论文为一篇名叫《基于项目的协同过滤推荐算法》(Item-BasedCollaborativeFilteringRecommendationAlgorithms)。摘要Recommendersystemsapplyknowledg......
  • mongo prisma
    prisma要求mongo服务必须是集群,需要有副本集执行事务。本地开发环境安装了一个debian12虚拟机:在debian12系统中, 安装docker,docker-compose下载mongo镜像:dockerpullmongo创建配置文件docker-compose.yml:version:'3'services:mongo:container_name:mong......
  • Django框架之视图层
    一、三板斧的原理介绍1、HttpResponse在Django中,HttpResponse是一个类,用于构建HTTP响应并返回给客户端。当视图函数处理完请求后,需要返回一个响应时,就会使用HttpResponse对象。(1)创建HttpResponse对象fromdjango.httpimportHttpResponseresponse=HttpResponse(content="......
  • Django框架之模型层
    一、前期准备1、测试脚本当你只是想要测试Django中的某一个py文件内容,那么你可以不用书写前后端交互的形式,而是直接写一个测试脚本即可这内容其实就是最外部manage.py文件中的上面几句话脚本代码无论是写在应用下的tests.py文件还是自己新建文件,将内容写在新文件中,都会生效......