• golang反射


    反射#

    有时我们需要写一个函数,这个函数有能力统一处理各种值类型,而这些类型可能无法共享同一个接口,也可能布局未知,也有可能这个类型在我们设计函数时还不存在,这个时候我们就可以用到反射

    反射是指在程序运行期间对程序本身进行访问和修改的能力。正常情况程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们

    反射的功能#

    1、反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别

    2、如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的方法、结构体的 tag。

    3、通过反射,可以修改变量的值,可以调用关联的方法

    Go语言中的变量是分为两部分的:

    类型信息:预先定义好的元信息。

    值信息:程序运行过程中可动态变化的。

    在 GoLang 的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。

    在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并 且 reflect 包 提 供 了 reflect.TypeOfreflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type

    reflect.TypeOf() 获取任意值类型对象#

    使用 reflect.TypeOf()函数可以接受任意 interface{}参数,可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息

    在反射中关于类型还划分为两种:类型(Type)和种类(Kind)

    因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,

    当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。

    Go 语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空

    
    import (
    	"fmt"
    	"reflect"
    )
    
    
    
    func reflectFn(x interface{}) {
    	// 反射获取任意变量类型
    	v := reflect.TypeOf(x)
    	name := v.Name() // 类型名称
      kind := v.Kind() // 种类(底层类型)
    	fmt.Printf("类型是%v,类型名称是%v,种类是%v \n", v, name, kind)
    
    }
    
    // 自定义一个myInt类型
    type myInt int
    
    // Person结构体
    type Person struct {
    	Name string
    	Age  int
    }
    
    func main() {
    	a := 10
    	b := "s"
    
    	// 打印基本类型
    	reflectFn(a) // 类型是int,类型名称是int,种类是int
    	reflectFn(b) // 类型是string,类型名称是string,种类是string
    
    	// 打印自定义类型和结构体类型
    	var i myInt = 3
    	var p = Person{
    		"1",
    		2,
    	}
    
    	reflectFn(i) // 类型是main.myInt,类型名称是myInt,种类是int
    	reflectFn(p) // 类型是main.Person,类型名称是Person,种类是struct
    
    	// 打印指针类型
    	var f = 25
    	reflectFn(&f) // 类型是*int,类型名称是,种类是ptr
    
    }
    
    
    
    reflect.ValueOf() 获取原始值#

    reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相转换

    image-20240225153112288

    通过反射获取原始值#
    func reflectValue(x interface{}) {
       // 通过反射获取到值
       v := reflect.ValueOf(x)
       fmt.Printf("%v, %T \n", v, v) // 10, reflect.Value,获取到的类型是reflect.Value
    
       fmt.Printf("%v,%T", v.Int(), v.Int()) // 10,int64,获取到原始值和类型,因为传入是数字,所以是v.Int()
    }
    
    func main() {
       var i = 10
       reflectValue(i)
    
    func reflectValue(x interface{}) {
    	v := reflect.ValueOf(x)
    	kind := v.Kind() // 获取种类
    
    	// 判断底层种类  
    	switch kind {
    	// 是int类型
    	case reflect.Int64:
    		fmt.Println(v.Int())
    	case reflect.Float64:
    		fmt.Println(v.Float())
    	case reflect.String:
    		fmt.Println(v.String())
    		
    	
    	}
    
    }
    
    通过反射设置变量的值#
    import (
    	"fmt"
    	"reflect"
    )
    
    func reflectValue(x interface{}) {
    	v := reflect.ValueOf(x)
    
      // 如果传入的值是一个指针地址,那需要通过Elem().Kind()获取具体种类,Elem()返回 Type 对象所表示的指针指向的数据
    	// 如果只是v.kind,获取的到的是指针类型
    	kind := v.Elem().Kind() // int64
    	if kind == reflect.Int64 {
    		//  int是SetInt  string是SetString等
    		v.Elem().SetInt(3)
    	}
    }
    
    func main() {
    	var i = 10
    
    	// 值类型修改副本不会影响原始值,所以需要通过指针地址
    	reflectValue(&i)
    	fmt.Println(i) // 3
    
    }
    

    结构体反射#

    任意值通过 reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField()和 Field()方法获得结构体成员的详细信息

    image-20240225160352855

    通过类型变量获取结构体的属性信息#
    import (
    	"fmt"
    	"reflect"
    )
    
    // studen结构体
    type Student struct {
    	Name  string `json:"name"`
    	Age   int    `json:"age"`
    	Score int    `json:"score"`
    }
    
    // 结构体方法 获取学生信息
    func (s Student) GetInfo() string {
    	return fmt.Sprintf("姓名%v 年龄%v 成绩%v", s.Name, s.Age, s.Score)
    
    }
    
    // 结构体方法 修改学生信息
    func (s *Student) SetInfo(name string, age, score int) {
    	s.Name = name
    	s.Age = age
    	s.Score = score
    
    }
    
    // 结构体方法 打印
    func (s Student) PrintInfo() {
    	fmt.Println("print info")
    
    }
    
    func PrintStructField(s interface{}) {
    	// 获取类型对象
    	t := reflect.TypeOf(s)
      /*
    
    		通过类型变量里面的Field获取结构体字段
    
    	*/
    	// 通过类型变量.Field(下标)可以获取结构体的字段对象
    	field0 := t.Field(0)
    
    
    	// 获取到结构体的Name字段对象
    	fmt.Println(field0) // {Name  string json:"name" 0 [0] false}
    
    	fmt.Printf("%#v \n", field0) // reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0xe748c80), Tag:"json:\"name\"", Offset:0x0, Index:[]int{0}, Anonymous:false}
    	// 获取字段名称
    	fmt.Println(field0.Name) // Name
    	// 获取字段类型
    	fmt.Println(field0.Type) // string
    	// 获取字段tag - json类型
    	fmt.Println(field0.Tag.Get("json")) // name
    
    	/*
    		通过类型变量里面的FieldByName获取结构体字段
    	*/
    	// FieldByName返回两个值,一个是具体的值,一个是是否成功
    	field1, ok := t.FieldByName("Age")
    	if ok {
    
    		fmt.Println(field1.Name)            // 字段名称:Age
    		fmt.Println(field1.Type)            // 字段类型:int
    		fmt.Println(field1.Tag.Get("json")) // 字段的json类型的tag :age
    	}
    
    	/*
    	  通过类型变量的NumField获取该结构体有多少个字段
    	*/
    
    	var count = t.NumField()
    	fmt.Println(count) // 3
    }
    
    func main() {
    
    	var student = Student{"小李", 15, 100}
    	PrintStructField(student)
    
    }
    
    
    通过值变量获取结构体值的值信息#
    func PrintStructField(s interface{}) {	
    	/*
    	 通过值变量获取结构体属性对应的值
    	*/
    	v := reflect.ValueOf(s)
    	// 方式一
    	fmt.Println(v.FieldByName("Name")) // 小李
    	fmt.Println(v.FieldByName("Age"))  // 15
    	// 方式二  可以通过循环获取所有的属性值
    	fmt.Println(v.Field(2)) // 100
      
    }
    
    通过反射修改结构体的属性值#
    func ReflectSetValue(s interface{}) {
    	// 类型对象
    	t := reflect.TypeOf(s)
    	// 值对象
    	v := reflect.ValueOf(s)
    	// 判断传入的结构体是否是指针类型,因为非指针是值类型,不能修改值,必现使用指针类型
    	if t.Kind() != reflect.Ptr {
    		fmt.Println("传入的不是指针类型")
    		return
    
    		// 判断传入的指针是不是结构体指针
    	} else if t.Elem().Kind() != reflect.Struct {
    		fmt.Println("传入的不是结构体指针")
    		return
    	}
    	// 获取Name字段的指针值
    	name := v.Elem().FieldByName("Name")
    	// 修改属性值
    	name.SetString("小王")
    }
    
    func main() {
    
    	var student = Student{"小李", 15, 100}
    	// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
    	ReflectSetValue(&student)
    	fmt.Println(student.Name) // 小王
    
    }
    
    
    通过类型变量获取结构体方法#
    // 打印结构体方法
    func PrintStructFn(s interface{}) {
    	t := reflect.TypeOf(s)
    
    	// 通过类型变量的Method(下标)获取结构体的方法
    	method0 := t.Method(0)    // 下标取的方法和结构体的顺序没有关系,和结构体的ASCII码有关系
    	fmt.Println(method0.Name) // 下标为0的方法名:GetInfo
    	fmt.Println(method0.Type) // func(main.Student) string
    
    	// 通过MethodByName 获取结构体方法
    	// 该方法两个返回值,一个是方法名,一个是是否有该方法的状态
    	method1, ok := t.MethodByName("PrintInfo")
    	if ok {
    		fmt.Println(method1.Name) // PrintInfo
    
    		fmt.Println(method1.Type) // func(main.Student)
    
    	}
    	// 获取结构体一共有几个方法
    	fmt.Println(t.NumMethod()) // 2 Student结构体定义了3个方法,有两个接收者类型是结构体,有一个接收者类型是指针接收者,如果s接收的是值接收者,只能统计值接收者的方法数量,如果是指针接收者,可以统计所有的方法的数量
    
    }
    
    func main() {
    
    	var student = Student{"小李", 15, 100}
    	PrintStructFn(student)
    
    }
    
    
    通过值变量执行结构体方法#
    func RunStructFn(s interface{}) {
    	v := reflect.ValueOf(s)
    	// 方法一 通过下标执行对应的方法,Call传递对应参数,nil代表没有参数
    	v.Method(1).Call(nil) // print info
    
    	// 方法二 通过MethodByName执行对应方法,返回值是一个切片
    	// 传入参数调用,Call方法传入的参数需要是一个reflect.Value类型的切片
    	var params []reflect.Value
    	params = append(params, reflect.ValueOf("小李"))
    	params = append(params, reflect.ValueOf(20))
    	params = append(params, reflect.ValueOf(95))
    	v.MethodByName("SetInfo").Call(params)
    
    	fmt.Println(v.MethodByName("GetInfo").Call(nil)) // [姓名小李 年龄20 成绩95]
    
    }
    
    func main() {
    
    	var student = Student{"小李", 15, 100}
    	// 如果传入一个值接收者,没有办法修改结构体的值,如果要修改对应值,需要传入指针接收者
    	RunStructFn(&student)
    
    }
    
    
  • 相关阅读:
    笔试练习day01
    【排序算法】归并排序(C语言)
    一种基于双MCU协同的多功能押解脚环
    C语言题目要求实现方法总结(1-10)
    pytorch 实现逻辑回归
    用DIV+CSS技术设计的美食主题网站(web前端网页制作课作业)美食餐饮网站设计与实现(HTML+CSS+JavaScript)
    瑞芯微rk3568移植openbmc(三)
    C语言中的数组使用方法及案例分享
    后端Long型数据传到前端js后精度丢失的问题
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校学生社团管理系统9p5w4
  • 原文地址:https://www.cnblogs.com/Mickey-7/p/18032760