Go语言 8 反射
文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/
Go学习群:415660935
8.1概念和作用
Reflection(反射)在计算机中表示程序能够检查自身结构的能力,它是元编程的一种形式。通过反射,可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。
通过反射机制在运行时能够完成如下功能:
1.确认对象的类
2.确认对象的类型的所有成员变量和方法
3.动态调用对象的方法
8.2 基本用法
本质上来说,反射就是一种检查接口变量的类型和值的机制。在Go语言中使用反射,首先要了解两个基本类型—reflect.Type和reflect.Value。对任何变量进行反射,都可以得到一个包含Type和Value的信息结构。顾名思义,Type主要表达的是被反射的这个变量本身的类型信息,而Value则为该变量的值。
1. 反射第一定律:反射可以将“接口类型对象”转换为“反射类型对象”
以上两个函数的参数都是接口类型,可以接收任何类型的变量。所以,称为接口类型变量。返回值是Type和Value,这两个类型称为反射类型,所以通过这两个函数可以将“接口类型变量”即参数转换为反射类型对象即返回值。
2 反射第二定律:反射可以将“反射类型对象”转换为“接口类型对象”
根据一个 reflect.Value 类型的变量,我们可以使用 Interface( )方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。
这两个定律互为逆操作。案例如下:
|
// reflect.go package main import ( "fmt" "reflect" ) func main() { // 1 根据接口类型的变量获取反射类型对象 var pi float64 = 3.14 t := reflect.TypeOf(pi) v := reflect.ValueOf(pi) fmt.Println(t, v) // 2 根据反射类型对象获取接口类型变量 pi = v.Interface().(float64) fmt.Println(pi) } |
3 反射第三定律:如果要修改“反射类型对象”,其值必须是“可写的”
通过反射定律一可以知道,反射对象包含了接口变量中存储的值以及类型。如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值;如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的。
|
package main import ( "fmt" "reflect" ) func main() { var num int = 10 fmt.Println(num) /* // 3.1 v := reflect.ValueOf(num) // 将num作为参数传递给ValueOf后,执行SetInt函数,num的值没有改变 v.SetInt(100) */ /* // 3.2 将num作为参数传递给ValueOf后,执行SetInt函数,修改的是num的地址,不是num的值 v := reflect.ValueOf(&num) v.SetInt(100) */ // 3.3 调用Elem(),相等于解引用 *(&num) v := reflect.ValueOf(&num).Elem() v.SetInt(100) fmt.Println(num) } |
4 对结构体的反射操作
4.1 遍历结构体的成员并修改成员的值
|
package main import ( "fmt" "reflect" ) type Person struct { Name string Age int } func main() { // 1 p1是对象 p1 := Person{"李四", 20} v := reflect.ValueOf(&p1).Elem() // 1 p1是对象的地址 //p1 := &Person{"李四", 20} //v := reflect.ValueOf(p1).Elem() num := v.NumField() for i := 0; i < num; i++ { fmt.Println(v.Type().Field(i).Name, v.Field(i).Type(), v.Field(i).Interface()) } //修改结构体中字段的值 v.Field(0).SetString("王五") v.Field(1).SetInt(30) fmt.Println(p1) } |
4.2 遍历结构体的方法并调用方法
|
// reflect.go package main import ( "fmt" "reflect" "strconv" ) type Person struct { Name string Age int } func (p *Person) SetName(name string) { p.Name = name } func (p *Person) SetAge(age int) { p.Age = age } func (p *Person) ToString() string { return "Name:" + p.Name + "Age:" + strconv.Itoa(p.Age) } func main() { // 5 打印和调用结构体的方法 p2 := &Person{"李四", 20} // 方法的接收器是指针,如果方法的接收器是对象不需要取地址 pv := reflect.ValueOf(&p2).Elem() num := pv.NumMethod() fmt.Println(num) for i := 0; i < num; i++ { fmt.Println(pv.Type().Method(i).Type) //方法类型 fmt.Println(pv.Type().Method(i).Name) //方法名称 } params := make([]reflect.Value, 1) params[0] = reflect.ValueOf(35) pv.Method(0).Call(params) params[0] = reflect.ValueOf("张飞") pv.Method(1).Call(params) info := pv.MethodByName("ToString").Call(nil) fmt.Println(info[0]) } |
疑问:反射结构体的方法时,采用结构体指针变量的地址作为ValueOf参数可以遍历到接收器为变量的指针的方法和为变量本身的方法。但是使用采用结构体指针变量作为ValueOf参数只能遍历到接收器为变量本身的方法。
5反射的综合例子:
|
// reflect.go package main import ( "fmt" "reflect" "strconv" ) type Person struct { Name string Age int } func (p *Person) SetName(name string) { p.Name = name } func (p *Person) SetAge(age int) { p.Age = age } func (p *Person) ToString() string { return "Name:" + p.Name + "Age:" + strconv.Itoa(p.Age) } // 1 根据接口类型的对象获取反射类型对象 func PrintInfo(object interface{}) (v reflect.Value) { t := reflect.TypeOf(object) v = reflect.ValueOf(object) fmt.Println(t, v) return v } // 2 根据反射类型对象获取接口类型对象 func ValueToObject(v reflect.Value) interface{} { return v.Interface() } func main() { // 1 根据接口类型对象获取反射类型对象 var pi float64 = 3.14 v1 := PrintInfo(pi) var p1 = Person{"张三", 18} v2 := PrintInfo(p1) fmt.Printf("%p\n", &p1) // 2 根据反射类型对象Value获取接口类型对象 pi = ValueToObject(v1).(float64) fmt.Println(pi) p1 = ValueToObject(v2).(Person) fmt.Println(p1) p1.ToString() fmt.Printf("%p\n", &p1) // 3 如果要修改反射类型对象,其值必须是可写的 var num int = 10 fmt.Println(num) v := reflect.ValueOf(&num).Elem() v.SetInt(100) fmt.Println(num) // 4 打印和修改结构体中字段的信息 p2 := &Person{"李四", 20} v = reflect.ValueOf(p2).Elem() num = v.NumField() for i := 0; i < num; i++ { fmt.Println(v.Type().Field(i).Name, v.Field(i).Type(), v.Field(i).Interface()) } //修改结构体中字段的值 v.Field(0).SetString("王五") v.Field(1).SetInt(30) fmt.Println(*p2) // 5 打印和调用结构体的方法 pv := reflect.ValueOf(&p2).Elem() num = pv.NumMethod() fmt.Println(num) for i := 0; i < num; i++ { fmt.Println(pv.Type().Method(i).Type) //方法类型 fmt.Println(pv.Type().Method(i).Name) //方法名称 } params := make([]reflect.Value, 1) params[0] = reflect.ValueOf(35) pv.Method(0).Call(params) params[0] = reflect.ValueOf("张飞") pv.Method(1).Call(params) info := pv.MethodByName("ToString").Call(nil) fmt.Println(info[0]) } |
Go语言 8 反射的更多相关文章
- go语言通过反射获取和设置结构体字段值的方法
本文实例讲述了go语言通过反射获取和设置结构体字段值的方法.分享给大家供大家参考.具体实现方法如下: type MyStruct struct { N int } n := MyStruct{ 1 } ...
- Go语言之反射(一)
反射 反射是指在程序运行期对程序本身进行访问和修改的能力.程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分.在运行程序时,程序无法获取自身的信息.支持反射的语言可以在程序编译期将 ...
- 深度解密Go语言之反射
目录 什么是反射 为什么要用反射 反射是如何实现的 types 和 interface 反射的基本函数 反射的三大定律 反射相关函数的使用 代码样例 未导出成员 反射的实际应用 json 序列化 De ...
- Go语言之反射(二)
反射的值对象 反射不仅可以获取值的类型信息,还可以动态地获取或者设置变量的值.Go语言中使用reflect.Value获取和设置变量的值. 使用反射值对象包装任意值 Go语言中,使用reflect.V ...
- Go语言的反射
反射是语言里面是非常重要的一个特性,我们经常会看见这个词,但是对于反射没有一个很好的理解,主要是因为对于反射的使用场景不太熟悉. 一.理解变量的内在机制 1.类型信息,元信息,是预先定义好的,静态的. ...
- 列举java语言中反射的常用方法
package review;/*12:43 2019/7/21*/ import model.AnotherClass; import model.OneClassMore; import mode ...
- Go语言之反射(三)
结构体转JSON JSON格式是一种用途广泛的对象文本格式.在Go语言中,结构体可以通过系统提供的json.Marshal()函数进行序列化.为了演示怎么样通过反射获取结构体成员以及各种值的过程,下面 ...
- go语言之反射
---恢复内容开始--- 一 :并发基础 1 并发和并行 并发和并行是两个不同的概念: 并行意味着程序在任意时刻都是同时运行的: 并发意味着程序在单位时间内是同时运行的 详解: 并行就是在任一粒度的时 ...
- Go语言基础之反射
Go语言基础之反射 本文介绍了Go语言反射的意义和基本使用. 变量的内在机制 Go语言中的变量是分为两部分的: 类型信息:预先定义好的元信息. 值信息:程序运行过程中可动态变化的. 反射介绍 反射是指 ...
随机推荐
- 剖析Vue原理&实现双向绑定MVVM-1
本文能帮你做什么?1.了解vue的双向数据绑定原理以及核心代码模块2.缓解好奇心的同时了解如何实现双向绑定为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简化改造,相对较简陋,并未考 ...
- 【Python】python学习文件的序列化和反序列化
json和pickle序列化和反序列化 json是用来实现不同程序之间的文件交互,由于不同程序之间需要进行文件信息交互,由于用python写的代码可能要与其他语言写的代码进行数据传输,json支持所有 ...
- HDU4054_Hexadecimal View
水题.直接八位八位地枚举即可. 注意控制输出,注意读数的时候要把s中的全部元素置零. #include <iostream> #include <cstdio> #includ ...
- Testng 运行Cannot find class in classpath
用Testng运行多个class,结果报: org.testng.TestNGException: Cannot find class in classpath: Salesman at or ...
- Eclipse中使用git提交代码,报错Testng 运行Cannot find class in classpath的解决方案
一.查找原因方式 1.点击Project——>Clear...——>Build Automatically 2.查看问题 二.报错因素 1.提交.xlsx文件 2.提交时,.xlsx文件被 ...
- 题解 P2089 【烤鸡】
看到这个题一共也就pow(3,10)=59049次循环,那不就暴力了嘛! 虽然说正解是动归和搜索, 但是搜索和暴力枚举的差距真心不大(不好好学习qwq). 看到楼上又说到 答案需要数据存储的问题, 这 ...
- 【bzoj3295】[Cqoi2011]动态逆序对 树套树 线段树套替罪羊树
这个东西,关于某个数的逆序对数为在他之前比他大的在他之后比他小的数的数目的和,所以删除之前先把这个减去就好了 人傻自带超大常数,中间由于内存池开小了所以运行错误. #include<cstdio ...
- Linux内核设计与实现第四周读书笔记
第5章系统调用 5.1与内核通信 主要作用: 为用户控件提供了一种硬件的抽象接口. 保证了系统稳定性与安全性. 为用户空间&系统提供公共接口. 5.2API.POSIX和C库 一般情况,应用程 ...
- 解题:APIO 2008 免费道路
题面 我们发现我们可以很容易知道最终完成的生成树中有多少鹅卵石路,但是我们不好得到这棵生成树的结构,所以我们尽量“谨慎”地完成生成树·,最好是一点点加到我们要达到的标准而不是通过删掉一些东西来完成 我 ...
- 框架----Django之Form组件
Django的Form主要具有一下几大功能: 生成HTML标签 验证用户数据(显示错误信息) HTML Form提交保留上次提交数据 初始化页面显示内容 一.小试牛刀 1.创建Form类 from d ...