Go通过reflect.Value修改值

到目前为止,反射还只是程序中变量的另一种读取方式。然而,在本节中我们将重点讨论如何通过反射机制来修改变量。

回想一下,Go语言中类似x、x.f[1]和*p形式的表达式都可以表示变量,但是其它如x + 1和f(2)则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。

对于reflect.Values也有类似的区别。有一些reflect.Values是可取地址的;其它一些则不可以。考虑以下的声明语句:

 

x := 2 // value type variable? a := reflect.ValueOf(2) // 2 int no b := reflect.ValueOf(x) // 2 int no c := reflect.ValueOf(&x) // &x *int no d := c.Elem() // 2 int yes (x)

其中a对应的变量不可取地址。因为a中的值仅仅是整数2的拷贝副本。b中的值也同样不可取地址。c中的值还是不可取地址,它只是一个指针&x的拷贝。实际上,所有通过reflect.ValueOf(x)返回的reflect.Value都是不可取地址的。但是对于d,它是c的解引用方式生成的,指向另一个变量,因此是可取地址的。我们可以通过调用reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的Value。

我们可以通过调用reflect.Value的CanAddr方法来判断其是否可以被取地址:

 

fmt.Println(a.CanAddr()) // "false" fmt.Println(b.CanAddr()) // "false" fmt.Println(c.CanAddr()) // "false" fmt.Println(d.CanAddr()) // "true"

每当我们通过指针间接地获取的reflect.Value都是可取地址的,即使开始的是一个不可取地址的Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,slice的索引表达式e[i]将隐式地包含一个指针,它就是可取地址的,即使开始的e表达式不支持也没有关系。以此类推,reflect.ValueOf(e).Index(i)对应的值也是可取地址的,即使原始的reflect.ValueOf(e)不支持也没有关系。

要从变量对应的可取地址的reflect.Value来访问变量需要三个步骤。第一步是调用Addr()方法,它返回一个Value,里面保存了指向变量的指针。然后是在Value上调用Interface()方法,也就是返回一个interface{},里面包含指向变量的指针。最后,如果我们知道变量的类型,我们可以使用类型的断言机制将得到的interface{}类型的接口强制转为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了:

 

x := 2 d := reflect.ValueOf(&x).Elem() // d refers to the variable x px := d.Addr().Interface().(*int) // px := &x *px = 3 // x = 3 fmt.Println(x) // "3"

或者,不使用指针,而是通过调用可取地址的reflect.Value的reflect.Value.Set方法来更新对应的值:

 

d.Set(reflect.ValueOf(4)) fmt.Println(x) // "4"

Set方法将在运行时执行和编译时进行类似的可赋值性约束的检查。以上代码,变量和值都是int类型,但是如果变量是int64类型,那么程序将抛出一个panic异常,所以关键问题是要确保改类型的变量可以接受对应的值:

 

d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int

同样,对一个不可取地址的reflect.Value调用Set方法也会导致panic异常:

 

x := 2 b := reflect.ValueOf(x) b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value

这里有很多用于基本数据类型的Set方法:SetInt、SetUint、SetString和SetFloat等。

 

d := reflect.ValueOf(&x).Elem() d.SetInt(3) fmt.Println(x) // "3"

从某种程度上说,这些Set方法总是尽可能地完成任务。以SetInt为例,只要变量是某种类型的有符号整数就可以工作,即使是一些命名的类型、甚至只要底层数据类型是有符号整数就可以,而且如果对于变量类型值太大的话会被自动截断。但需要谨慎的是:对于一个引用interface{}类型的reflect.Value调用SetInt会导致panic异常,即使那个interface{}变量对于整数类型也不行。

 

x := 1 rx := reflect.ValueOf(&x).Elem() rx.SetInt(2) // OK, x = 2 rx.Set(reflect.ValueOf(3)) // OK, x = 3 rx.SetString("hello") // panic: string is not assignable to int rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int var y interface{} ry := reflect.ValueOf(&y).Elem() ry.SetInt(2) // panic: SetInt called on interface Value ry.Set(reflect.ValueOf(3)) // OK, y = int(3) ry.SetString("hello") // panic: SetString called on interface Value ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"

当我们用Display显示os.Stdout结构时,我们发现反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员,比如在类Unix系统上os.File结构体中的fd int成员。然而,利用反射机制并不能修改这些未导出的成员:

 

stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var fmt.Println(stdout.Type()) // "os.File" fd := stdout.FieldByName("fd") fmt.Println(fd.Int()) // "1" fd.SetInt(2) // panic: unexported field

一个可取地址的reflect.Value会记录一个结构体成员是否是未导出成员,如果是的话则拒绝修改操作。因此,CanAddr方法并不能正确反映一个变量是否是可以被修改的。另一个相关的方法CanSet是用于检查对应的reflect.Value是否是可取地址并可被修改的:

 

fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/150448.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

JVM技术文档--JVM诊断调优工具Arthas--阿里巴巴开源工具--一文搞懂Arthas--快速上手--国庆开卷!!

​ Arthas首页 简介 | arthas Arthas官网文档 Arthas首页、文档和下载 - 开源 Java 诊断工具 - OSCHINA - 中文开源技术交流社区 阿丹: 之前聊过了一些关于JMV中的分区等等,但是有同学还是在后台问我,还有私信问我,学了这些…

Java多线程篇(7)——AQS之共享锁(Semaphore、CountDownLatch)

文章目录 1、Semaphore1.1、acquire1.2、release 2、CountDownLatch2.1、await2.2、countDown 1、Semaphore 1.1、acquire Semaphore.acquire public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}AbstractQueuedSynchronizer.acquireSh…

pytorch算力与有效性分析

pytorch Windows中安装深度学习环境参考文档机器环境说明3080机器 Windows11qt_env 满足遥感CS软件分割、目标检测、变化检测的需要gtrs 主要是为了满足遥感监测管理平台(BS)系统使用的,无深度学习环境内容swin_env 与 qt_env 基本一致od 用于…

RabbitMQ之Direct(直连)Exchange解读

目录 基本介绍 使用场景 springboot代码演示 演示架构 工程概述 RabbitConfig配置类:创建队列及交换机并进行绑定 MessageService业务类:发送消息及接收消息 主启动类RabbitMq01Application:实现ApplicationRunner接口 基本介绍 在r…

【MySql】mysql之进阶查询语句

目录 一、常用查询 1、order by按关键字排序❤ 1.1 升序排序 1.2 降序排序 1.3 结合where进项条件过滤再排序 1.4 多字段排序 2、and和or判断 2.1 and和or的使用 2.2 嵌套、多条件使用 3、distinct 查询不重复记录 4、group by 对结果进行分组 5、limit限制结果…

K8S:配置资源管理 Secret和configMap

文章目录 一.Secret1.Secret概念2.Secret的类型①kubernetes.io/service-account-token②opaque③kubernetes.io/dockerconfigjson④kubernetes.io/tls 3.secret的三种参数①tls②docker-registry③generic 4.Pod 的3种方式来使用secret5.Secret创建及案例(1&#x…

算法题:柠檬水找零

这道题就是纯贪心算法题,遍历每个顾客,先把钱收了,如果是10块钱就判断手里头有没有5元用于找零;如果是20块钱,先判断是不是有10元5元,如果没有就再判断是否有3个5元。没有的话就直接返回 False。(完整题目附…

模型压缩部署概述

模型压缩部署概述 一,模型在线部署 1.1,深度学习项目开发流程 1.2,模型训练和推理的不同 二,手机端CPU推理框架的优化 三,不同硬件平台量化方式总结 参考资料 一,模型在线部署 深度学习和计算机视觉…

excel中将一个sheet表根据条件分成多个sheet表

有如下excel表,要求:按月份将每月的情况放在一个sheet中。 目测有6个月,就应该有6个sheet,每个sheet中体现本月的情况。 一、首先增加一个辅助列,月份,使用month函数即可。 填充此列所有。然后复制【月份】…

QT内存管理

Qt的半自动化的内存管理 (1)QObject及其派生类的对象,如果其parent非0,那么其parent析构时会析构该对象。 (2)QWidget及其派生类的对象,可以设置 Qt::WA_DeleteOnClose 标志位(当close时会析构…

代码随想录算法训练营第23期day14|二叉树层序遍历、226.翻转二叉树、101. 对称二叉树

目录 一、二叉树层序遍历 非递归法 递归法 相关题目(10题) 二、(leetcode 226)翻转二叉树 递归法 层序遍历 深度优先遍历 1)非统一写法——前序遍历 2) 统一写法——前序遍历 三、(le…

C++设计模式-享元(Flyweight)

目录 C设计模式-享元(Flyweight) 一、意图 二、适用性 三、结构 四、参与者 五、代码 C设计模式-享元(Flyweight) 一、意图 运用共享技术有效地支持大量细粒度的对象。 二、适用性 一个应用程序使用了大量的对象。完全由…

十一工具箱流量主小程序源码

无授权,去过滤机制版本 看到网上发布的都是要授权的 朋友叫我把他去授权,能用就行 就把过滤去了 这样就不用授权 可以免费使用 白嫖党专属 一切接口可用,无需担心不能用 授权者不关站一直可以用 源码下载:https://download.csdn.…

抄写Linux源码(Day19:读取硬盘前的准备工作有哪些?)

回忆我们需要做的事情: 为了支持 shell 程序的执行,我们需要提供: 1.缺页中断(不理解为什么要这个东西,只是闪客说需要,后边再说) 2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的,所以需要这两个东…

在WIN10平台上体验Microsoft古老的Quick C 1.0编程

前言: 90年代初,微软出了Quick系统对抗Borland Turbo系列,其中包括 QuickBasic, QuickPascal和Quick C。1991年,Quick C for Windows 1.0发布,后来它被Visual C取代。我自己觉得微软成就在那个winstub.exe桩上&#xf…

Connect to 127.0.0.1:1080 [/127.0.0.1] failed: Connection refused: connect

报错信息 A problem occurred configuring root project CourseSelection. > Could not resolve all artifacts for configuration :classpath.> Could not resolve com.android.tools.build:gradle:3.6.1.Required by:project :> Could not resolve com.android.tool…

【数据库——MySQL】(14)过程式对象程序设计——游标、触发器

目录 1. 游标1.1 声明游标1.2 打开游标1.3 读取游标1.4 关闭游标1.5 游标示例 2. 触发器2.1 创建触发器2.2 修改触发器2.3 删除触发器2.4 触发器类型2.5 触发器示例 参考书籍 1. 游标 游标一般和存储过程一起配合使用。 1.1 声明游标 要使用游标,需要用到 DECLAR…

C++:继承

本文主要从 继承的概念及定义 、基类和派生类对象赋值转换、继承中的作用域、派生类的默认成员函数、继承与友元、继承与静态成员 、复杂的菱形继承及菱形虚拟继承 、继承的总结和反思 方面介绍继承。 目录 一、继承的概念及定义 1.继承的概念 2.继承定义 1.定义格式 2.继…

抄写Linux源码(Day17:你的键盘是什么时候生效的?)

回忆我们需要做的事情: 为了支持 shell 程序的执行,我们需要提供: 1.缺页中断(不理解为什么要这个东西,只是闪客说需要,后边再说) 2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的,所以需要这两个东…

用IDEA操作数据库--MySQL

IDEA集成了DataGrip的操作数据库的功能 就可以省略我们下载SQLyog/Navicat/DataGrip这些图形化操作工具了 以下是IDEA的使用 输入数据库的用户和密码