Go语言反射(Json)

[toc]

😶‍🌫️go语言官方编程指南:https://golang.org/#open in new window

go语言的官方文档学习笔记很全,推荐去官网学习

😶‍🌫️我的学习笔记:github: https://github.com/3293172751/golang-rearnopen in new window


区块链技术(也称之为分布式账本技术),是一种互联网数据库技术,其特点是去中心化,公开透明,让每一个人均可参与的数据库记录

❤️💕💕关于区块链技术,可以关注我,共同学习更多的区块链技术。博客http://nsddd.topopen in new window


反射

反射的引子

有时候我们需要写一个函数,这个函数有能力处理各种值的类型。**反射是指在程序运行期对程序本身进行访问和修改的能力。**程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go程序在运行期使用reflect包访问程序的反射信息。

json的字符串也用了反射技术。

空接口可以存储任何类型的变量,那我们如何知道这个空接口保存的数据的类型是什么?

  • 类型断言
  • 可以使用反射实现

后面的ORM框架也可能用到反射技术

接口的命名规范

约定:

一般都是以er结尾 例如WriterReader

type Reader interface {
    Read(p []byte) (n int, err error)
}

两个函数的接口名综合两个函数名,如:

type WriteFlusher interface {
    Write([]byte) (int, error)
    Flush() error
}

三个以上函数的接口名类似于结构体名,如:

type Car interface {
    Start() 
    Stop()
    Drive()
}

使用refilect.TypeOf()获取任意值的类型对象

💡简单的一个案例如下:

  • Kind表示获取底层的类型
  • Type表示类型
/*
 * @Description:反射获取任意类型的值
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-10-04 21:37:41
 * @LastEditTime: 2022-10-26 16:08:30
 * @FilePath: \code\go-super\48-main.go
 * @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
 * Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
 */
package main

import (
	"fmt"
	"reflect"
)

type myInt int

type User struct {
	Id   int
	Name string
	Age  int
}

func (u *User) Hello(x interface{}) {
	v := reflect.ValueOf(x) //反射获取值
	fmt.Printf("类型名称:%v, 类型种类:%v, 类型值:%v", v, v.Type(), v.Kind())
	fmt.Println()
	//类型种类指的是值的类型,比如int,string,bool,struct,array,slice,map,chan,func,interface,ptr,unsafe.Pointer
}

func main() {
	u := &User{1, "OK", 12}
	u.Hello(u)

	u.Hello(123)
	u.Hello(12.123)

	var i = [3]int{1, 2, 3} // i是数组
	u.Hello(i)

	var y = []int{1, 2, 3} // y是切片
	u.Hello(y)

	var m = map[string]int{"a": 1, "b": 2} // m是map
	u.Hello(m)

}

🚀 编译结果如下:

[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\48-main.go"
类型名称:&{1 OK 12}, 类型种类:*main.User, 类型值:ptr
类型名称:123, 类型种类:int, 类型值:int
类型名称:12.123, 类型种类:float64, 类型值:float64
类型名称:[1 2 3], 类型种类:[3]int, 类型值:array
类型名称:[1 2 3], 类型种类:[]int, 类型值:slice
类型名称:map[a:1 b:2], 类型种类:map[string]int, 类型值:map

比如在写适配器函数的时候,我们此时就需要用到反射标记

反射的一个基本入门

💡简单的一个案例如下:

/*
 * @Description: json
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-10-04 21:37:41
 * @LastEditTime: 2022-10-24 19:37:14
 * @FilePath: \code\go-super\23-main.go
 * @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
 * Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
 */
package main

import (
	"encoding/json"
	"fmt"
)

type Config struct {
	ID      string   `json:"id"`
	Genders []string `json:"性别"`
	Age     int      `json:"年龄"`
	Name    string   `json:"姓名"`
}

type ConfigList struct {
	Configs []Config `json:"configs"`
	Config
	Email string `json:"email"`
}

func main() {
	configList := ConfigList{
		Configs: []Config{
			{
				ID:      "1",
				Genders: []string{"asfd", "asfd"},
				Age:     20,
				Name:    "Tom",
			},
			{
				ID:      "2",
				Genders: []string{"asfd", "asfd", "asfd"},
				Age:     30,
				Name:    "Jack",
			},
		},
		Config: Config{
			ID:      "3",
			Genders: []string{"asfd", "asfd", "asfd", "as"},
			Age:     40,
			Name:    "Rose",
		},
		Email: "	nsddd.top",
	}
	fmt.Println(configList)

	//遍历
	for _, configList := range configList.Configs {
		fmt.Println(configList)
	}

	//json
	jsonBody, err := json.Marshal(configList)
	if err != nil {
		fmt.Println("err=", err)
	}
	fmt.Println("jsonBody=", string(jsonBody))
}

🚀 编译结果如下:

[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\23-main.go"
{[{1 [asfd asfd] 20 Tom} {2 [asfd asfd asfd] 30 Jack}] {3 [asfd asfd asfd as] 40 Rose} 	nsddd.top}
{1 [asfd asfd] 20 Tom}
{2 [asfd asfd asfd] 30 Jack}
jsonBody= {"configs":[{"id":"1","性别":["asfd","asfd"],"年龄":20,"姓名":"Tom"},{"id":"2","性别":["asfd","asfd","asfd"],"年龄":30,"姓名":"Jack"}],"id":"3","性别":["asfd","asfd","asfd","as"],"年龄":40,"姓名":"Rose","email":"\tnsddd.top"}
json转化回来

💡简单的一个案例如下:

/*
 * @Description:json反序列
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-10-04 21:37:41
 * @LastEditTime: 2022-10-24 20:01:45
 * @FilePath: \code\go-super\24-main.go
 * @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
 * Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
 */
package main

import (
	"encoding/json"
	"fmt"
)

type Config struct {
	ID      string   `json:"id"`
	Genders []string `json:"性别"`
	Age     int      `json:"年龄"`
	Name    string   `json:"姓名"`
}

type ConfigList struct {
	Configs []Config `json:"configs"`
	Config
	Email string `json:"email"`
}

func main() {
	var c = &ConfigList{}

	//定义一个json
	jsonBody := `{"configs":[{"id":"1","性别":["asfd","asfd"],"年龄":20,"姓名":"Tom"},{"id":"2","性别":["asfd","asfd","asfd"],"年龄":30,"姓名":"Jack"}],"id":"3","性别":["asfd","asfd","asfd","as"],"年龄":40,"姓名":"Rose","email":"\tnsddd.top"}`

	if err := json.Unmarshal([]byte(jsonBody), c); err != nil {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println("json反序列化失败" + err.(string))
			}
		}()

	} else {
		fmt.Println("json反序列化成功")
		fmt.Println(c)
	}
}

🚀 编译结果如下:

json反序列化成功
&{[{1 [asfd asfd] 20 Tom} {2 [asfd asfd asfd] 30 Jack}] {3 [asfd asfd asfd as] 40 Rose} 	nsddd.top}

反射的基本介绍

反射的基本介绍

关于反射有以下需要知道的:

  1. 反射可以在运行的时候动态获取变量的各种信息,比如说变量的类型(type),类别(kind)
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段,方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法
  4. 使用反射,需要用到一个包import(“reflect”)

🖱️ 打开包网页open in new window

package reflect

包反射实现运行时反射,允许程序操作任意类型的对象。典型的用法是取一个静态类型 interface{} 的值,通过调用 TypeOf 提取其动态类型信息,返回一个 Type。

对 ValueOf 的调用会返回一个表示运行时数据的值。Zero 接受一个 Type 并返回一个 Value,表示该类型的零值。

所以反射我们可以取出值但是没有办法进行操作,编译器通不过,此时需要进行断言

我们通过反射获取到type

type Typeopen in new window open in new window

type Type interface {
    // Kind返回该接口的具体分类
    Kind() Kind
    // Name返回该类型在自身包内的类型名,如果是未命名类型会返回""
    Name() string
    // PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
    // 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
    PkgPath() string
    // 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
    // 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较。
    String() string
    // 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
    Size() uintptr
    // 返回当从内存中申请一个该类型值时,会对齐的字节数
    Align() int
    // 返回当该类型作为结构体的字段时,会对齐的字节数
    FieldAlign() int
    // 如果该类型实现了u代表的接口,会返回真
    Implements(u Type) bool
    // 如果该类型的值可以直接赋值给u代表的类型,返回真
    AssignableTo(u Type) bool
    // 如该类型的值可以转换为u代表的类型,返回真
    ConvertibleTo(u Type) bool
    // 返回该类型的字位数。如果该类型的Kind不是Int、Uint、Float或Complex,会panic
    Bits() int
    // 返回array类型的长度,如非数组类型将panic
    Len() int
    // 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
    Elem() Type
    // 返回map类型的键的类型。如非映射类型将panic
    Key() Type
    // 返回一个channel类型的方向,如非通道类型将会panic
    ChanDir() ChanDir
    // 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
    NumField() int
    // 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
    Field(i int) StructField
    // 返回索引序列指定的嵌套字段的类型,
    // 等价于用索引中每个值链式调用本方法,如非结构体将会panic
    FieldByIndex(index []int) StructField
    // 返回该类型名为name的字段(会查找匿名字段及其子字段),
    // 布尔值说明是否找到,如非结构体将panic
    FieldByName(name string) (StructField, bool)
    // 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
    FieldByNameFunc(match func(string) bool) (StructField, bool)
    // 如果函数类型的最后一个输入参数是"..."形式的参数,IsVariadic返回真
    // 如果这样,t.In(t.NumIn() - 1)返回参数的隐式的实际类型(声明类型的切片)
    // 如非函数类型将panic
    IsVariadic() bool
    // 返回func类型的参数个数,如果不是函数,将会panic
    NumIn() int
    // 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
    In(i int) Type
    // 返回func类型的返回值个数,如果不是函数,将会panic
    NumOut() int
    // 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
    Out(i int) Type
    // 返回该类型的方法集中方法的数目
    // 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
    // 匿名字段导致的歧义方法会滤除
    NumMethod() int
    // 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    Method(int) Method
    // 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
    // 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
    // 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
    MethodByName(string) (Method, bool)
    // 内含隐藏或非导出方法
}

通过反射我们获取到reflect.Type类型,通过这个类型我们可以使用方法反向操作

反射应用场景

  1. 不知道接口调用的是哪个函数

  2. 对结构体序列化时,如果结构体有指定tag,也会使用到反射生成对应的字符串

反射重要概念❤️

1 . reflect.TypeOf(变量名),获取变量类型,返回的是reflect.Type类型

  1. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型,是一个结构体类型,通过它可以获取到该变量的更多信息

  2. 变量、interface{}(空接口)和reflect.Value是可以相互转换的,这点在实际开发中经常用到

var student Stu //结构体
var num int    //变量

//专门用作反射函数
func test(b interface{}){
    //如何将interface转化为reflect.Value
    rVal := reflect.ValueOf(b)  //获取变量值
    
    //如何将reflect.Value转成空接口类型
    iVal := rVal.interface()
    
    //如何将interface空接口转为原变量类型 - - 使用类型断言即可
    v := iVal.(Stu)       //直接转化类型断言到变量
}

反射入门案列

演示对基本数据类型,空接口和反射基本操作

/*************************************************************************
    > File Name: reflect.go
    > Author: smile
    > Mail: 3293172751nss@gmail.com 
    > Created Time: Wed 23 Mar 2022 04:19:19 PM CST
 ************************************************************************/

 package main
 import(
    "fmt"
    "reflect"
)

func reflectInterface(b interface{}){
    //通过反射 获取到传入的变量的type kind value 
    //rType r开头的表示这种type是
    rType := reflect.TypeOf(b)
    fmt.Println("rType = ",rType) //打印出int

    //获取到reflect类型
    rVal := reflect.ValueOf(b)
    fmt.Println("rVal = ",rVal) //打印出数字100,虽然是100但不是普通的100,rval 不能进行计算

    //查看rVal的真正类型
    fmt.Printf("rVal = %v  rVal type = %T\n ",rVal,rVal) //打印出int

    //我们看下文档,对rVal进行计算
    // n1 := 12  //不能使用n1变量进行加减
    //如果类型是float,那么此时需要用到断言
    n2 := 11 + rVal.Int()
    fmt.Println("n2 = ",n2)

    //我们已经把变量交给接口,而且拿到了反射的value,那么此时怎么把它重新转化为intterface{}
    iv := rVal.Interface()
    //将interface通过断言转化为需要的类型
    num2 := iv.(int)
    fmt.Println("num2 = ",num2)
    fmt.Println("iv = ",iv)
}

//专门演示反射,对结构体的反思
func reflectTest(b interface{}){
    //rType r开头的表示这种type是
    rType := reflect.TypeOf(b)
    fmt.Println("rType = ",rType) //打印出int

    //获取到reflect类型
    rVal := reflect.ValueOf(b)
    fmt.Println("rVal = ",rVal) //打印出数字100,虽然是100但不是普通的100,rval 不能进行计算


    //我们已经把变量交给接口,而且拿到了反射的value,那么此时怎么把它重新转化为intterface{}
    iv := rVal.Interface()

    fmt.Printf("iv = %v iv type = %T",iv,iv) 
    fmt.Println("")
    
    //通过断言转化为需要的数据类型,通过swich或者使用简单的类型断言
    stu,ok := iv.(Student)
    if ok {
        fmt.Println("student.Name = ",stu.Name)
    }
    fmt.Println("student",b.(Student))
    fmt.Println("student.Name = ",b.(Student).Name)
}

type Student struct{
    Name string 
    Age int

}
func main(){
    //基本操作

    var num int = 100

    reflectInterface(num)
    fmt.Println("分割线","---------------------------------------")
    stu := Student{
        Name : "tom",
        Age : 20,
    }

    reflectTest(stu)
}

编译

[root@mail golang]# go run  reflect.go
rType =  int
rVal =  100
rVal = 100  rVal type = reflect.Value
 n2 =  111
num2 =  100
iv =  100
分割线 ---------------------------------------
rType =  main.Student
rVal =  {tom 20}
iv = {tom 20} iv type = main.Student
student.Name =  tom
student {tom 20}
student.Name =  tom

常量

使用const修饰
定义必须初始化
不能修改
只能修饰bool string int float类型

简介写法

const(
    a = 1
    b = 2
)

专业写法

const(
    a = inta          //a等于0
    b 				  //b等于1
    c				  //c等于2
)

类似于枚举哈哈

反射注意事项

通过反射可以让变量在interface{}和Reflect.Value之间相互转换

如果要通过反射来修改变量,注意当使用SetXxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到redlect.Value.Elem()方法

注意:elme

func (Value) Elem
func (v Value) Elem() Value

Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。

package main
import(
	"redlect"
	"fmt"
)

func main(){
    var str string = "tom" 
    fs := reflect.ValueOf(&str)
    fs.Elem().SetString("jack")
    fmt.Printf("%v\n",str)          //jack
}

反射最佳案例

  1. 使用反射来遍历结构体字段,调用结构体的方法,并获取结构体标签的值

方法

func (v Value) Method(i int) Value
func (v Value) Call(in []Value) []Value

案例

package main
import (
	"fmt"
	"reflect"
)
//定义了一个Monster结构体
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{}) {
	//获取reflect.Type 类型
	typ := reflect.TypeOf(a)
    
	//获取reflect.Value 类型
	val := reflect.ValueOf(a)
    
	//获取到a对应的类别
	kd := val.Kind()
	//如果传入的不是struct,就退出     --  判断是否是结构体
	if kd !=  reflect.Struct {
		fmt.Println("expect struct")
		return
	}

	//获取到该结构体有几个字段
	num := val.NumField()

	fmt.Printf("struct has %d fields\n", num) //4
	//变量结构体的所有字段
	for i := 0; i < num; i++ {
		fmt.Printf("Field %d: 值为=%v\n", i, val.Field(i))
		//获取到struct标签, 注意需要通过reflect.Type来获取tag标签的值
		tagVal := typ.Field(i).Tag.Get("json")
        //typ中的field方法中的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()) //返回结果, 返回的结果是 []reflect.Value*/
}

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

编译

[root@mail go-eth]# go run  reflect.go
struct has 4 fields
Field 0: 值为=黄鼠狼精
Field 0: tag为=name
Field 1: 值为=400
Field 1: tag为=monster_age
Field 2: 值为=30.8
Field 2: tag为=成绩
Field 3: 值为=
struct has 3 methods
---start~----
{黄鼠狼精 400 30.8 }
---end~----
res= 50

结构体反射相关的方法

注意

结构体相关的方法如下:

任意类型的反射值都有一个方法叫做Type,它返回一个Type类型的值,这个Type类型的值代表了反射值的动态类型。 Type类型的值有一个方法叫做Kind,它返回一个Kind类型的值,这个Kind类型的值代表了反射值的静态类型。 Kind类型的值有一个方法叫做String,它返回一个字符串,这个字符串代表了反射值的静态类型的名称。

reflect.Type中与结构体成员相关的方法如下:

  • NumField:返回结构体成员的数量。
  • Field:返回结构体成员的信息。
  • FieldByIndex:返回结构体成员的信息。
  • FieldByName:返回结构体成员的信息。
  • FieldByNameFunc:返回结构体成员的信息。
💡简单的一个案例如下:

判断参数是否是结构体类型:

我们可以先判断,符合标准就继续,不符合标准就直接return返回就好了

/*
 * @Description:与结构体反射相关的方法
 * @Author: xiongxinwei 3293172751nss@gmail.com
 * @Date: 2022-10-04 21:37:41
 * @LastEditTime: 2022-10-26 17:16:24
 * @FilePath: \code\go-super\51-main.go
 * @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
 * Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
 */
package main

import (
	"errors"
	"fmt"
	"reflect"
)

/*
## 结构体反射相关的方法
任意类型的反射值都有一个方法叫做`Type`,它返回一个`Type`类型的值,这个`Type`类型的值代表了反射值的动态类型。
`Type`类型的值有一个方法叫做`Kind`,它返回一个`Kind`类型的值,这个`Kind`类型的值代表了反射值的静态类型。
`Kind`类型的值有一个方法叫做`String`,它返回一个字符串,这个字符串代表了反射值的静态类型的名称。

reflect.Type中与结构体成员相关的方法如下:
- `NumField`:返回结构体成员的数量。
- `Field`:返回结构体成员的信息。
- `FieldByIndex`:返回结构体成员的信息。
- `FieldByName`:返回结构体成员的信息。
- `FieldByNameFunc`:返回结构体成员的信息。
*/

type Person interface {
	GetInfo() (string, error)
	SetInfo(string, int, float64) error
}

type Student struct {
	Name  string  `json:"name"`
	Age   int     `json:"age"`
	Score float64 `json:"score"`
}

func (s Student) GetInfo() (string, error) {
	var str = fmt.Sprintf("name:%s,age:%d,score:%f", s.Name, s.Age, s.Score) 
    //获取结构体成员的值
	return str, nil
}

func (s *Student) SetInfo(name string, age int, score float64) error {
	s.Name = name
	s.Age = age
	s.Score = score
	return errors.New("set info error")
}

// print info
func printInfo(s Person) { //Person类型表示了一个接口类型,接口类型的值可以是任意类型的值,所以这里的s可以是任意类型的值
	t := reflect.TypeOf(s)     //获取s的动态类型
	v := reflect.ValueOf(s)    //获取s的动态值
	fmt.Println("动态类型:", t.Name	1	(), "静态类型:", t.Kind().String())           //获取s的静态类型
	fmt.Println("动态值:", v, "静态值:", v.Elem())                             //获取s的静态值
	if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct { //判断s的动态型类是否是结构体类型
		fmt.Println("s is not struct")
		return
	}

	//获取结构体成员的数量
	fmt.Println("结构体成员的数量:", t.Elem().NumField())

	//获取结构体成员的信息
	for i := 0; i < t.Elem().NumField(); i++ {
		fmt.Println("结构体成员的信息:", t.Elem().Field(i))
	}

	//获取方法数量
	fmt.Println("方法数量:", t.Elem().NumMethod())

	//获取方法的参数数量
	fmt.Println("方法的参数数量:", t.Elem().Method(0).Type.NumIn())

	//获取方法的返回值数量
	fmt.Println("方法的返回值数量:", t.Elem().Method(0).Type.NumOut())

	//判断结构体有没有这个方法
	metadata, ok := t.Elem().MethodByName("GetInfo")
	if ok {
		fmt.Println("结构体没有GetInfo方法")
	} else {
		fmt.Println("结构体有多少方法:", metadata)
	}

}

func main() {
	study := &Student{
		"张三",
		18,
		100,
	}
	var s Person = study
	s.SetInfo("李四", 20, 99.21)

	printInfo(s)

}

🚀 编译结果如下:

[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\51-main.go"
动态类型:  静态类型: ptr
动态值: &{李四 20 99.21} 静态值: {李四 20 99.21}
结构体成员的数量: 3
结构体成员的信息: {Name  string json:"name" 0 [0] false}
结构体成员的信息: {Age  int json:"age" 16 [1] false}
结构体成员的信息: {Score  float64 json:"score" 24 [2] false}
方法数量: 1
方法的参数数量: 1
方法的返回值数量: 2
结构体没有GetInfo方法