首页 > 其他分享 >golang反射

golang反射

时间:2024-02-25 19:11:07浏览次数:26  
标签:反射 获取 fmt golang 类型 reflect func Println

反射

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

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

反射的功能

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)

}

标签:反射,获取,fmt,golang,类型,reflect,func,Println
From: https://www.cnblogs.com/Mickey-7/p/18032760

相关文章

  • golang 系统调用与阻塞处理
    所有在UNIX系统上运行的程序最终都会通过C系统调用来和内核打交道。用其他语言编写程序进行系统调用,方法不外乎两个:一是自己封装,二是依赖glibc、或者其他的运行库。Go语言选择了前者,把系统调用都封装到了syscall包。封装时也同样得通过汇编实现。当M一旦进入系统调用后,会......
  • golang io优化
    如果想兼顾开发效率,又能保证高并发,协程就是最好的选择。它可以在保持异步化运行机制的同时,用同步方式写代码(goroutine-per-connection),这在实现高并发的同时,缩短了开发周期,是高性能服务未来的发展方向。CPU和IO设备是不同的设备,能并行运行。合理调度程序,充分利用硬件,就能跑出......
  • golang性能优化
    性能优化流程理清待优化代码的常用逻辑与场景根据实际场景编写压测用例使用pprof或者火焰图等工具取得数据找到热点代码重点优化Profilingpprof是用于可视化和分析性能分析数据的工具。为什么pprof可以帮助我们分析Go程序性能呢?因为它可以采集程序运行时数据:比如说协程栈,这样服......
  • Golang Swag 注释
    常用的注释用法:@title:指定API的标题。@description:对API的简要描述。@version:API的版本号。@termsOfService:API的使用条款。@contact:API的联系方式,例如邮箱。@license:API的许可证信息。@host:API的主机名和端口号。@BasePath:API的基本路径。@S......
  • 刘铁猛C#学习笔记21 反射与依赖注入
    反射对于一个现有对象,能够在不使用new操作符、不知道其具体静态类型的情况下,创建出一个同类型的对象,还能访问这个复制品对象拥有的各个成员。相当于进一步的解耦,可以不使用new操作符,就没有类型的依赖关系这样的耦合关系甚至可以弱到忽略不计反射在.NET和JAVA中非常重要 ......
  • golang中协程&管道&锁
    进程和线程进程(Process)就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间。一个进程至少有5种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态,通......
  • Unity编辑器扩展秘籍-反射解决ParticleSystemEditor的扩展显示错误的问题
    如果使用常规的扩展编辑器方法,为ParticleSystem增加一个自定义按钮[CustomEditor(typeof(ParticleSystem))]publicclassMyParticleSystemEditor:UnityEditor.Editor{privateList<Material>_mats=newList<Material>();publicoverridevoi......
  • golang中的接口(数据类型)
    golang中的接口Golang中的接口是一种抽象数据类型,Golang中接口定义了对象的行为规范,只定义规范不实现。接口中定义的规范由具体的对象来实现,通俗的讲接口就一个标准,它是对一个对象的行为和规范进行约定,约定实现接口的对象必须得按照接口的规范接口的定义在go中接口(int......
  • golang中的类型断言,解释.(float64)和.(string)
    在Go语言中,. 后跟括号中的类型名称(如 .(float64) 或 .(string))通常出现在类型断言(typeassertion)的上下文中。类型断言用于检查一个空接口(interface{})值是否包含特定的类型,如果是,则将其转换为该类型。类型断言的语法如下:value,ok:=x.(T)其中 x 是一个 interface{}......
  • Golang Gorm 的标签tag
    当使用GORM进行数据库模型映射时,可以使用多种标签来定义字段的行为。以下是一些常用的GORM标签:gorm:"primary_key":定义字段作为模型的主键。gorm:"column:<column_name>":指定字段在数据库表中的列名。gorm:"type:<data_type>":指定字段的数据库数据类型。gorm:"......