介绍
这个是在B站上看边看视频边做的笔记,这一章是Glang反射的基本介绍、反射快速入门、反射的最佳实践等
配套视频自己去B站里面搜【go语言】,最高的播放量就是视频地址
里面的注释我写的可能不太对,欢迎大佬们指出╰(°▽°)╯
(十三)、反射
-
反射的使用场景
-
使用反射机制,编写函数的适配器, 桥连接
一、反射的基本介绍
1.基本介绍
-
反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
-
如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
-
通过反射,可以修改变量的值,可以调用关联的方法。
-
使用反射,需要
import ("reflect")
-
示意图
2.反射的应用场景
反射常见应用场景有以下两种
-
不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。例如以下这种桥接模式,比如我前面提出问题。
func bridge(funcPtc interface{}, ages ...interface{})
第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数
-
对结构体序列化时,如果结构体有指定Tag,也会使用到反射生成对应的字符串。
3.反射重要的函数和概念
-
reflect.Typeof(变量名),获取变量的类型,返回reflect.Type类型
-
reflect.Valueof(变量名),获取变量的值,返回reflect.Value类型reflect.Value 是一个结构体类型。[看文档]通过reflect.Value,可以获取到关于该变量的很多信息。
-
变量、interface{} 和reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到。
示意图:
二、反射的快速入门
-
请编写一个案例,演示对(基本数据类型、
interface{}
、reflect.Value
)进行反射的基本操作package main import ( "fmt" "reflect" ) // 演示基本数据类型的反射 func reflectTest(b interface{}) { //通过反射获取传入的变量type类型,kind和值 rTyp := reflect.TypeOf(b) //获取到b空接口传入的reflectType类型 fmt.Println("rType =", rTyp) //显示num类型 rVal := reflect.ValueOf(b) //获取到b空接口传入的reflectValue值 fmt.Println("rVal =", rVal) //显示num的值 //尝试用reflectValue获取到的值直接和int进行加减 // n1 := 2 + rVal //错误 n1 := 2 + rVal.Int() //转换为int fmt.Println(n1) //将rVal转换成interface{} iV := rVal.Interface() //将interface{}通过断言转换成需要的类型 n2 := iV.(int) fmt.Println(n2) } func main() { //请编写一个案例,演示对(基本数据类型、`interface{}`、`reflect.Value`)进行反射的基本操作 //1.定义一个int var num int = 100 reflectTest(num) }
-
请编写一个案例,演示对(结构体类型、
interface{}
、reflect.Value
)进行反射的基本操作package main import ( "fmt" "reflect" ) type Student struct { Name string Age int } // 演示对结构体的反射 func reflectTest(b interface{}) { rTyp := reflect.TypeOf(b) //获取b的类型 rVal := reflect.ValueOf(b) //获取b的值 fmt.Printf("类型=%T 值=%v\n", rTyp, rVal) iV := rVal.Interface() //转换类型 fmt.Printf("rVal类型=%T\n", rVal) //打印原始类型 fmt.Printf("iV类型=%T\n", iV) //打印转换后的类型 //使用类型断言获取struct结构体内的数据 stu, ok := iV.(Student) if ok { fmt.Printf("stu.Name=%v stu.Age=%v", stu.Name, stu.Age) } } func main() { //定义一个student的实例 stu := Student{ Name: "tom", Age: 32, } reflectTest(stu) }
三、反射的注意事项和细节
-
reflect.Value.Kind
,获取变量的类别,返回的是一个常量//获取变量对应的kind fmt.Println(rVal.Kind()) fmt.Println(rTyp.Kind())
//常量在声明的时候必须给值 var num int const tax int = 0 fmt.Println(num,tax) //常量是不能修改的 //tax = 10 //错误 //常量只能修饰bool、数值类型(int, float系列)、string 类型 //常量不能接收变量,只能接收确定的值
-
Type 和Kind 的区别
Type 是类型, Kind 是类别, Type 和Kind 可能是相同的,也可能是不同的.
比如:
var num int = 10
,num
的Type是int
,Kind
也是int
比如:
var stu Student
,stu
的Type是pkg1.Student
,Kind是struct
-
通过反射可以在让变量在
interface{}
和Reflect.Value
之间相互转换//使用类型断言获取struct结构体内的数据 stu, ok := iV.(Student) if ok { fmt.Printf("stu.Name=%v stu.Age=%v", stu.Name, stu.Age) }
-
使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配,比如
n1
是int
,那么就应该使用reflect.Value(x).Int()
,而不能使用其它的,否则报panicn1 := 2 + rVal.Int() //转换为int
-
通过反射的来修改变量, 注意当使用SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到
reflect.Value.Elem()
方法package main import ( "fmt" "reflect" ) // 通过反射,修改 num int 的值 func reflect01(b interface{}) { //传入的是地址 rVal := reflect.ValueOf(b) //获取b的值 fmt.Printf("rVal的kind是:%v\n", rVal.Kind()) //查看rVal的类型kind是一个指针类型 rVal.Elem().SetInt(20) //使用Elem修改num的值 fmt.Println(rVal) } func main() { var num int = 10 fmt.Println("num的默认值是:", num) //传入前的值 reflect01(&num) //传入地址 fmt.Println("num的默认值是:", num) //传入后的值 }
-
reflect.Value.Elem()
应该如何理解?
四、反射练习
-
给你一个变量
var v float64 = 1.2
, 请使用反射来得到它的reflect.Value
, 然后获取对应的Type
,
Kind
和值,并将reflect.Value
转换成interface{}
, 再将interface{}
转换成float64
-
看段代码,判断是否正确,为什么
package main import ( "fmt" "reflect" ) func main() { var str string = "tom" fs := reflect.ValueOf(str) fs.SetString("jack") fmt.Printf("%v\n", str) }
修改
package main import ( "fmt" "reflect" ) func main() { var str string = "tom" //ok fs := reflect.ValueOf(&str) //传入指针 fs.Elem().SetString("jack") //使用Elem() fmt.Printf("%v\n", str) }
五、反射最佳实践
-
使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
package main import ( "fmt" "reflect" ) //定义一个结构体 type Monster struct { Name string `json:"name"` Age int `json:"age"` Score float32 //`json:"score"` Sex string //`json:"sex"` } //创建一个方法显示s的值 func (s Monster) Print() { fmt.Println("start-----") fmt.Println(s) fmt.Println("end-----") } //创建一个方法,返回2个数的和 func (s Monster) GetSum(n1 int, n2 int) int { return n1 + n2 } //接受4个值,给s赋值 func (s *Monster) SetMonster(name string, age int, score float32, sex string) { s.Name = name s.Age = age s.Score = score s.Sex = sex } //使用反射案例 func TestStruct(a interface{}) { / typ := reflect.TypeOf(a) //获取a的类型 val := reflect.ValueOf(a) //获取a的值 kd := val.Kind() //获取val的kind类型 if kd != reflect.Struct { //判断类型是否是struct结构体,不是就退出 fmt.Println("传入的参数不是struct类型") return } / / num := val.NumField() //获取到该结构体有几个字段 fmt.Printf("struct结构体有%v个字段\n", num) //struct结构体有4个字段 for i := 0; i < num; i++ { //遍历结构体所有字段 fmt.Printf("使用Value值获取第%v个字段值是=%v\n", i, val.Field(i)) //打印对应字段的值 //获取struct结构体的标签,注意,要通过reflect.Type来获取tag标签的值 //通过类型获取标签 tagVal := typ.Field(i).Tag.Get("json") //获取i个字段 ,获取json标签 if tagVal != "" { //判断是否有标签,有标签就输出 fmt.Printf("使用tag标签获取第%v个字段是=%v\n", i, tagVal) } } / / //获取该结构体有多少个方法 numOfMethod := val.NumMethod() fmt.Printf("结构体有%v个方法\n", numOfMethod) //调用方法//获取到第2个方法 val.Method(1).Call(nil) //方法的排序默认是按照函数名(ASCII)来排序 //调用到第1个方法Method(0) var params []reflect.Value //声明了[]reflect.Value切片 params = append(params, reflect.ValueOf(10)) //把int类型的10转换为reflect.ValueOf,使用切片赋值追加给params params = append(params, reflect.ValueOf(40)) //把int类型的40转换为reflect.ValueOf,使用切片赋值追加给params res := val.Method(0).Call(params) //把对应的的切片[]reflect.Value值传入给第1个方法, fmt.Println("res =", res[0].Int()) //并返回结果切片[]reflect.Value / } func main() { monster := Monster{ Name: "tom", Age: 21, Score: 2.3, Sex: "哈哈", } TestStruct(monster) }
-
使用反射的方式来获取结构体的tag 标签, 遍历字段的值,修改字段值,调用结构体方法(要求:通过传递地址的方式完成, 在前面案例上修改即可)
package main import ( "fmt" "reflect" ) // 定义一个结构体 type Monster struct { Name string `json:"name"` Age int `json:"age"` Score float32 //`json:"score"` Sex string //`json:"sex"` } // 使用反射,把tom改为汤姆 func TestStruct(a interface{}) { // typ := reflect.TypeOf(a) //获取类型 val := reflect.ValueOf(a) //获取值 kd := val.Kind() //获取值的类型,传入的是指针 fmt.Println(kd) //ptr num := val.Elem().NumField() //获取struct结构体有多少个字段,使用Elem() fmt.Println(num) //4 //打印所有字段 for i := 0; i < num; i++ { fmt.Printf("第%v个字段值是=%v\n", i, val.Elem().Field(i)) } //开始修改,把tom改为汤姆 //使用Elem()获取原值的封装,使用Field定位修改的字段,Set你要修改的类型 val.Elem().Field(0).SetString("汤姆") } func main() { monster := Monster{ Name: "tom", Age: 21, Score: 2.3, Sex: "哈哈", } //修改tom为汤姆 TestStruct(&monster) //传入地址 fmt.Println(monster) }
-
定义了两个函数test1 和test2,定义一个适配器函数用作统一处理接口
-
使用反射操作任意结构体类型
-
使用反射创建并操作结构体
1.练习
要求:
-
编写一个Cal 结构体,有 两个字段 Num1,和Num2
-
方法
GetSub(name string)
-
使用反射遍历Cal结构体所有的字段信息
-
使用反射机制完成对GetSub 的调用,输出形式为"tom 完成了减法运行,8 - 3 = 5"
不齐:
package main
import (
"fmt"
"reflect"
)
type Cal struct {
Num1 int
Num2 int
}
func GetSub(name string) {
}
// 使用反射遍历Cal结构体所有的字段信息
func Calreflect1(a interface{}) {
//显示Cal所有信息
typ := reflect.TypeOf(a)
val := reflect.ValueOf(a)
// kd := val.Kind()
num := val.NumField()
for i := 0; i < num; i++ {
fmt.Printf("第%v个字段%v对应的值是%v\n", i, typ.Field(i).Name, val.Field(i))
}
}
// 使用反射机制完成对GetSub 的调用,输出形式为"tom 完成了减法运行,8 - 3 = 5"
func Calreflect2(b interface{}) {
val := reflect.ValueOf(b)
kd := val.Kind()
fmt.Println(kd)
}
func main() {
num := Cal{
Num1: 21,
Num2: 12,
}
Calreflect1(num)
GetSub("tom")
}
章节目录
【Golang第1~3章:基础】如何安装golang、第一个GO程序、golang的基础
【Golang第4章:函数】Golang包的引用,return语句、指针、匿名函数、闭包、go函数参数传递方式,golang获取当前时间
【Golang第5章:数组与切片】golang如何使用数组、数组的遍历和、使用细节和内存中的布局;golang如何使用切片,切片在内存中的布局
【Golang第6章:排序和查找】golang怎么排序,golang的顺序查找和二分查找,go语言中顺序查找二分查找介绍和案例
【Golang第7章:map】go语言中map的基本介绍,golang中map的使用案例,go语言中map的增删改查操作,go语言对map的值进行排序
【Golang第8章:面向对象编程】Go语言的结构体是什么,怎么声明;Golang方法的调用和声明;go语言面向对象实例,go语言工厂模式;golang面向对象的三大特性:继承、封装、多态
【Golang第9章:项目练习】go项目练习家庭收支记账软件项目、go项目练习客户管理系统项目
【Golang第10章:文件操作】GO语言的文件管理,go语言读文件和写文件、GO语言拷贝文件、GO语言判断文件是否存在、GO语言Json文件格式和解析
【Golang第12章:goroutine协程与channel管道】GO语言goroutine协程和channel管道的基本介绍、goroutine协