Go语言反射(Json)
[toc]
😶🌫️go语言官方编程指南:https://golang.org/#
go语言的官方文档学习笔记很全,推荐去官网学习
😶🌫️我的学习笔记:github: https://github.com/3293172751/golang-rearn
区块链技术(也称之为分布式账本技术),是一种互联网数据库技术,其特点是去中心化,公开透明,让每一个人均可参与的数据库记录
❤️💕💕关于区块链技术,可以关注我,共同学习更多的区块链技术。博客http://nsddd.top
反射
反射的引子
有时候我们需要写一个函数,这个函数有能力处理各种值的类型。**反射是指在程序运行期对程序本身进行访问和修改的能力。**程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go程序在运行期使用reflect包访问程序的反射信息。
json的字符串也用了反射技术。
空接口可以存储任何类型的变量,那我们如何知道这个空接口保存的数据的类型是什么?
- 类型断言
- 可以使用反射实现
后面的ORM框架也可能用到反射技术
接口的命名规范
约定:
一般都是以er
结尾 例如Writer
、Reader
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/3293172751/cs-awesome-Block_Chain
* 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/3293172751/cs-awesome-Block_Chain
* 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/3293172751/cs-awesome-Block_Chain
* 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}
反射的基本介绍
反射的基本介绍
关于反射有以下需要知道的:
- 反射可以在运行的时候动态获取变量的各种信息,比如说变量的类型(type),类别(kind)
- 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段,方法)
- 通过反射,可以修改变量的值,可以调用关联的方法
- 使用反射,需要用到一个包import(“reflect”)
package reflect
包
包反射实现运行时反射,允许程序操作任意类型的对象。典型的用法是取一个静态类型 interface{} 的值,通过调用 TypeOf 提取其动态类型信息,返回一个 Type。
对 ValueOf 的调用会返回一个表示运行时数据的值。Zero 接受一个 Type 并返回一个 Value,表示该类型的零值。
所以反射我们可以取出值但是没有办法进行操作,编译器通不过,此时需要进行断言
我们通过反射获取到type
Type ¶
typetype 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
类型,通过这个类型我们可以使用方法反向操作
反射应用场景
不知道接口调用的是哪个函数
对结构体序列化时,如果结构体有指定tag,也会使用到反射生成对应的字符串
反射重要概念❤️
1 . reflect.TypeOf(变量名),获取变量类型,返回的是reflect.Type类型
reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型,是一个结构体类型,通过它可以获取到该变量的更多信息
变量、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
}
反射最佳案例
- 使用反射来遍历结构体字段,调用结构体的方法,并获取结构体标签的值
方法
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/3293172751/cs-awesome-Block_Chain
* 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方法