Go第 17 章 :反射

Go第 17 章 :反射

17.1 先看一个问题,反射的使用场景

请添加图片描述

17.2 使用反射机制,编写函数的适配器, 桥连接

请添加图片描述

17.3 反射的基本介绍

17.3.1 基本介绍
  1. 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  4. 使用反射,需要 import (“reflect”)
  5. 示意图
    请添加图片描述
17.3.2 反射的应用场景

请添加图片描述

17.3.3 反射重要的函数和概念

请添加图片描述
3) 变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。画 出示意图
请添加图片描述
请添加图片描述

17.4 反射的快速入门

17.4.1 快速入门说明

 请编写一个案例,演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作
代码演示,见下面的表格:
 请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作
代码演示:

package main

import (
	"fmt"
	"reflect"
)

//专门演示反
func reflectTest(b interface{}) {
	//通过反射获取的传入的变量的type,kind值
	//1、先获取到reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)
	//2、获取到reflect.Value
	rVal := reflect.ValueOf(b)

	n2 := 2 + rVal.Int()
	fmt.Println("n2=", n2)
	fmt.Printf("rVal=%v rVal type=%T\n", rVal, rVal)

	//3、下面将rVal转程interface{}
	iV := rVal.Interface()
	//将interface{}通过类型断言转成需要的类型
	num2 := iV.(int)
	fmt.Printf("num2=%d", num2)

	return
}

//专门演示反射【对结构体的反射】
func reflectTest02(b interface{}) {
	//通过反射获取的传入的变量type,kind值
	//1、先获取到reflect.Type
	rType := reflect.TypeOf(b)
	fmt.Println("rType=", rType)
	//2、获取到reflect.Value
	rVal := reflect.ValueOf(b)
	fmt.Println("rValue=", rVal)
	//下面我们将 rVal 转成 interface{}
	iV := rVal.Interface()
	fmt.Printf("iv=%v iv type=%T \n", iV, iV)
	//将 interface{} 通过断言转成需要的类型
	//这里,我们就简单使用了一带检测的类型断言.
	//这里,我们就简单使用了一带检测的类型断言.
	//同学们可以使用 swtich 的断言形式来做的更加的灵活
	stu, ok := iV.(Student)
	if ok{
		fmt.Printf("stu.Name=%v\n", stu.Name)
	}

}

type Student struct {
	Name string
	Age  int
}
type Monster struct {
	Name string
	Age  int
}

func main() {
	//请编写一个案例,
	//演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作
	//1. 先定义一个 int
	var num int = 100
	reflectTest(num)
	//2. 定义一个 Student 的实例
	//stu := Student{
	//	Name: "tom",
	//	Age:  20,
	//}
	//reflectTest02(stu)
}

17.5 反射的注意事项和细节

1) reflect.Value.Kind,获取变量的类别,返回的是一个常量

请添加图片描述

2) Type 和 Kind 的区别

Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也可能是不同的.
比如: var num int = 10 num 的 Type 是 int ,
Kind 也是 int 比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct

3)4)

请添加图片描述

对结构体需要进行一下类型断言才能使用reflect.Value(x).Int()
5) 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法

请添加图片描述

6) reflect.Value.Elem() 应该如何理解?

请添加图片描述

17.6 反射课堂练习

  1. 给你一个变量 var v float64 = 1.2 , 请使用反射来得到它的 reflect.Value, 然后获取对应的 Type, Kind 和值,并将 reflect.Value 转换成 interface{} , 再将 interface{} 转换成 float64. [不说:]
package main

import (
	"fmt"
	"reflect"
)

func main(){
	var v float64 = 1.2
	rVal:=reflect.ValueOf(v)
	fmt.Println("rVal=",rVal)
	rType:=reflect.TypeOf(v)
	fmt.Println("rType=",rType)
	rKind:=reflect.Kind(v)
	fmt.Println("rKind=",rKind)

	rinterface:=rVal.Interface()
	fmt.Printf("rinterface=%v  rinterface Type=%T",rinterface,rinterface)
}

  1. 看段代码,判断是否正确,为什么
    package main
    import (
    “fmt”
    “reflect”
    )
    func main() {
    var str string = “tom” //ok
    fs := reflect.ValueOf(str)
    //ok
    fs -> string
    fs.SetString(“jack”)
    //error
    fmt.Printf(“%v\n”, str)
    }
![请添加图片描述](https://img-blog.csdnimg.cn/544a34747b7f4c16989a3c1c01e00b76.png)
## 17.7 反射最佳实践
![请添加图片描述](https://img-blog.csdnimg.cn/2b3af851a20f4ce1a70fb653a515e899.png)

#### 1) 使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
###### 反射调用方法的排名是通过对象绑定的方法名的首字母的ASCLL码来排列的 PSG
````go
package main

import (
	"fmt"
	"reflect"
)

type Monster struct {
	Name  string  `json:"name"`
	Age   int     `json:monster_age`
	Score float32 `json:"成绩"`
	Sex   string
}

//方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}

//方法 接收四个值,给s赋值
func (s Monster) Set(name string, age int, score float32, sex string) {
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}

//方法显示s的值
func (s Monster) Print() {
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}

func TestStruct(a interface{}) {
	//获取reflcet.Type类型
	typ := reflect.TypeOf(a)
	fmt.Println("typ=", typ)
	//获取reflcet.Vale类型
	val := reflect.ValueOf(a)
	fmt.Println("val=", val)
	//找到a对应的类别
	kd := val.Kind()
	fmt.Println("kind=", kd)
	//如果传入的不是struct,就退出
	if kd != reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//获取到该结构体有几个字段
	num := val.NumField()
	fmt.Println("num=", num)
	//变量结构体的所有字段
	for i := 0; i < num; i++ {
		fmt.Printf("Filed %d: 值为:%v\n", i, val.Field(i))
		//获取到struct标签,注意需要通过reflect.Type来获取tag标签的值
		tagVal := typ.Field(i).Tag.Get("json")
		//如果该字段于 tag 标签就显示,否则就不显示
		if tagVal != "" {
			fmt.Printf("Field %d: tag 为=%v\n", i, tagVal)
		}
	}

	//获取到该结构体有多少个方法
	numOfMethod := val.NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)

	//var params []reflect.Value
	//方法的排序默认是按照 函数名的排序(ASCII 码)
	val.Method(1).Call(nil) //获取到第二个方法。调用它

	//调用结构体的第 1 个方法 Method(0)
	var params []reflect.Value //声明了[]reflect.Value
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Value
	fmt.Println("res=", res[0].Int())
}

func main() {
	//创建了一个 Monster 实例
	var a Monster = Monster{
		Name:  "黄鼠狼精",
		Age:   400,
		Score: 30.8,
	}
	//将 Monster 实例传递给 TestStruct 函数
	TestStruct(a)
}

  1. 使用反射的方式来获取结构体的 tag 标签, 遍历字段的值,修改字段值,调用结构体方法(要求: 通过传递地址的方式完成, 在前面案例上修改即可)
package main

import (
	"fmt"
	"reflect"
)

type Monster struct {
	Name  string  `json:"name"`
	Age   int     `json:"monster_age"`
	Score float32 `json:"成绩"`
	Sex   string
}

//方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {
	return n1 + n2
}

//方法 接收四个值,给s赋值
func (s Monster) Set(name string, age int, score float32, sex string) {
	s.Name = name
	s.Age = age
	s.Score = score
	s.Sex = sex
}

//方法显示s的值
func (s Monster) Print() {
	fmt.Println("---start---")
	fmt.Println(s)
	fmt.Println("---end---")
}

func TestStruct(a interface{}) {
	//获取reflcet.Type类型
	typ := reflect.TypeOf(a)
	fmt.Println("typ=", typ)
	//获取reflcet.Vale类型
	val := reflect.ValueOf(a)
	fmt.Println("val=", val)
	//找到a对应的类别
	kd := val.Kind()
	fmt.Println("kind=", kd)
	//如果传入的不是struct,就退出
	//要多一层判断,判断是否是指针,并且还要用elem去获取指针的信息(*ptr)
	//根据获取的数据再去通过kind去获取传入的类型是什么,进而去对比
	if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
		fmt.Println("expect struct")
		return
	}
	//获取到该结构体有几个字段
	num := val.Elem().NumField()//因为是指针,我们这里获取字段的长度前面也要加个elem表示这个值是一个指针
	fmt.Println("num=", num)
	val.Elem().Field(0).SetString("tianyi")//我们通过setStringi去修改索引位0的值,同样要加Elem

	//变量结构体的所有字段
	for i := 0; i < num; i++ {
		fmt.Printf("Filed %d: 值为:%v\n", i, val.Elem().Field(i))
		//获取到struct标签,注意需要通过reflect.Type来获取tag标签的值
		tagVal := typ.Elem().Field(i).Tag.Get("json")//只要是获取这个指针的值信息的,都要带elem
		//如果该字段于 tag 标签就显示,否则就不显示
		if tagVal != "" {
			fmt.Printf("Field %d: tag 为=%v\n", i, tagVal)
		}
	}

	tag :=typ.Elem().Field(0).Tag.Get("json")
	fmt.Printf("tag=%s\n",tag)
	//获取到该结构体有多少个方法
	numOfMethod := val.Elem().NumMethod()
	fmt.Printf("struct has %d methods\n", numOfMethod)

	//var params []reflect.Value
	//方法的排序默认是按照 函数名的排序(ASCII 码)
	val.Elem().Method(1).Call(nil) //获取到第二个方法。调用它

	//调用结构体的第 1 个方法 Method(0)
	var params []reflect.Value //声明了[]reflect.Value
	params = append(params, reflect.ValueOf(10))
	params = append(params, reflect.ValueOf(40))
	res := val.Method(0).Call(params) //传入的参数是[]reflect.Value,返回[]reflect.Value
	fmt.Println("res=", res[0].Int())
}

func main() {
	//创建了一个 Monster 实例
	var a Monster = Monster{
		Name:  "黄鼠狼精",
		Age:   400,
		Score: 30.8,
	}
	//将 Monster 实例传递给 TestStruct 函数
	TestStruct(&a)
	fmt.Println(a)
}

typ= *main.Monster
val= &{黄鼠狼精 400 30.8 }
kind= ptr
num= 4
Filed 0: 值为:tianyi
Field 0: tag 为=name
Filed 1: 值为:400
Field 1: tag 为=monster_age
Filed 2: 值为:30.8
Field 2: tag 为=成绩
Filed 3: 值为:
tag=name
struct has 3 methods
---start---
{tianyi 400 30.8 }
---end---
res= 50
{tianyi 400 30.8 }

  1. 定义了两个函数 test1 和 test2,定义一个适配器函数用作统一处理接口【了解】
package main

import (
	"fmt"
	"reflect"
)

func Call1(v1 ,v2 int){
	fmt.Println(v1/v2)
}
func Call2(v1,v2 int,str string){
	fmt.Println(v1+v2,str)
}

//定义一个通用的函数适配器
//通过args...interface{}用于接收多个参数
func bridge(call interface{},args ...interface{}){
	n:=len(args)//先统计传入的参数长度
	inValue:=make([]reflect.Value,n)//根据传入的参数数量 创建一个typ Value的切片

	for i:=0;i<n;i++{//因为传入的是多个参数,接收到的是切片
		inValue[i]=reflect.ValueOf(args[i])//循环将接收到的参数 以常量的形式加入到[]reflect.Value切片中
	}
	function:=reflect.ValueOf(call)//将传入的函数转换为Value结构体类型
	function.Call(inValue)//使用Value Call方法直接执行传入的函数,并将[]Value的inValue作为参数交给函数使用

}
func main(){
	bridge(Call1,1,2)
	bridge(Call2,1,2,"test2")
}
  1. 使用反射操作任意结构体类型:【了解】
package main

import (
	"fmt"
	"reflect"
)

type user struct{
	UserId string
	Name string
}

func main(){
	model := &user{}
	sv := reflect.ValueOf(model)
	fmt.Println("reflect.ValueOf",sv.Kind().String())

	sv =sv.Elem()//可以再这里做一下 指针指定,下面就不用指定了
	fmt.Println("reflect.ValueOf.Elem",sv.Kind().String())

	//FiledByName指定要修改的字段
	//SetString修改字段信息
	sv.FieldByName("UserId").SetString("12345678")
	sv.FieldByName("Name").SetString("nickname")
	fmt.Println("model",model)
}
  1. 使用反射创建并操作结构体
package main

import (
	"fmt"
	"reflect"
)

type user1 struct{
	UserId string
	Name string
}

func main(){
	var(
		model *user1
		//st reflect.Type
		//elem reflect.Value
	)

	st := reflect.TypeOf(model)
	fmt.Println("reflect.Typeof",st.Kind().String())//ptr

	st = st.Elem()
	fmt.Println("reflect.Typeof.Elem",st.Kind().String())//struct

	//定义一个结构体
	elem := reflect.New(st)//这里的New返回一个Value类型值,该值持有一个指向类型为st的新申请的零值的指针
							//返回值的Type为PtrTo(st)
	fmt.Println("reflect.New",elem.Kind().String())//ptr
	fmt.Println("reflect.New.Elem",elem.Kind().String())//Struct
	//可以看到我们New出来的Value类型和上面提前定义的结构体类似

	model = elem.Interface().(*user1)//这里通过转换位空接口后类型推导为*uSer的结构体类型
	// 并将我们声明出来的这个结构体又赋予给了开头的model结构体变量
	//但是我们现在这个elem是一个内存地址啊,赋予到了model,那么elem就和model数据同步了
	elem = elem.Elem() //这里通过Elem这里将内存地址进行调用,类似*ptr
	//modeL现在拿的是elem给的值,我们还可以通过elem去动态修改其他值
	//当modeL或elem被修改时,对方也会同步数据
	elem.FieldByName("UserId").SetString("123456")
	elem.FieldByName("Name").SetString("tianyi")
	fmt.Println("model model.name",model,model.Name)
}

17.8 课后作业

请添加图片描述

package main

import (
	"fmt"
	"reflect"
)

type Cal struct {
	Num1 int
	Num2 int
}

func (c Cal)GetSub(name string){
	fmt.Println("tom完成了减法预算,",c.Num2-c.Num1)
}

func reflectCal (cal interface{}){
	rValue := reflect.ValueOf(cal)
	fmt.Println("reflect.ValueOf",rValue)
	filedNum := rValue.NumField()
	for i:=0;i<filedNum;i++{
		fmt.Printf("filed%d value = %v\n",i,rValue.Field(i))
	}
	MethodNum :=rValue.NumMethod()
	fmt.Printf("a has %d Methods \n",MethodNum)

	var params []reflect.Value
	params = append(params,reflect.ValueOf("tom"))
	rValue.Method(0).Call(params)
}


func main(){
	var cal =Cal{
		Num1:3,
		Num2:8,
	}

	reflectCal(cal)

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值