首页 > 其他分享 >golang进阶之反射

golang进阶之反射

时间:2024-03-08 17:47:19浏览次数:14  
标签:反射 进阶 fmt golang reflect 类型 type name

目录

一、 go中变量的内在机制

  • 在介绍反射之前,我们首先要了解的是Go语言中的变量的内在机制,因为反射就是基于这种机制实现的

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

    • 类型信息:预先定义好的元信息
    • 值信息:程序运行过程中通过赋值和初始化等操作而可动态变化的

二、反射

1. 反射是把双刃剑

  • 注意:反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个
    • 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后
    • 大量使用反射的代码通常难以理解
    • 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级

2. 反射的简介

  • 反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息

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

  • 在python中,反射是利用字符串在一个指定的对象中找到与该字符串相同名字的属性或方法。而GO语言中的反射是为了将给定的对象,利用反射的方法,求得其数据类型和值的信息,并且可以修改其值(如某个未知的结构体或者空接口中包含的内容,就可以用反射一步步取出其中的值,也能修改某个值)

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

三、reflect

  • 在Go语言的反射机制中,接口值都由是 一个具体类型具体类型的值 两部分组成的(在上一篇接口的博客中的类型断言中提到过)。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由 reflect.Typereflect.Value 两部分组成,
  • reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象TypeValue

1. reflect.TypeOf

  • 在Go语言中,使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var a float32 = 3.14
	v1 := reflect.TypeOf(a)
	fmt.Printf("type:%v\n", v1)  // type:float32

	var b int64 = 100
	v2 := reflect.TypeOf(b)
	fmt.Printf("type:%v\n", v2)  // type:int64
}

(1)reflect.Type的 name 和 kind

  • 在反射中关于类型还划分为两种:类型名(name)种类(Kind)。因为在Go语言中我们可以使用type关键字构造很多自定义类型,name 为 自定义类型的名字,而种类(Kind)就是指该自定义类型的底层的类型
  • 在反射中,一般基础数据类型变量, name 和 kind 是相同的(如 string、int、byte、bool 等类型的变量),而当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类
  • Go语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回
package main

import (
	"fmt"
	"reflect"
)

type myInt int64

func reflectType(x interface{}) {
	t := reflect.TypeOf(x)
	fmt.Printf("name:%v kind:%v\n", t.Name(), t.Kind())
}

func main() {
	var a *float32  // 指针
	var b myInt     // 自定义类型
	var c rune      // 类型别名
	var c1 byte     // 类型别名
	var f string    // 字符串
	var g int       // 整型
	var h bool      // 布尔型
	reflectType(a)  // name: kind:ptr
	reflectType(b)  // name:myInt kind:int64
	reflectType(c)  // name:int32 kind:int32
	reflectType(c1) // name:uint8 kind:uint8
	reflectType(f)  // name:string kind:string
	reflectType(g)  // name:int kind:int
	reflectType(h)  // name:bool kind:bool

	type person struct {
		name string
		age  int
	}
	type book struct{ title string }
	var d = person{
		name: "沙河小王子",
		age:  18,
	}
	var e = book{title: "《跟小王子学Go语言》"}
	reflectType(d)  // type:person kind:struct
	reflectType(e)  // type:book kind:struct

	type tt interface {
	}
	
    if l == nil {
		fmt.Println(55555)  // 55555
	}
    
	var k tt
	t := reflect.TypeOf(k)
	fmt.Println(t)  // <nil>   注意:reflect.TypeOf() 的参数是一个 nil 的接口类型时,则该函数返回 nil

}

(2)kind 的能返回的类型如下

  • reflect包中定义的Kind类型如下
type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

2. reflect.ValueOf

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

  • reflect.Value类型提供的获取原始值的方法如下:

    • 方法 说明
      Interface() interface {} 将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
      Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
      Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
      Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
      Bool() bool 将值以 bool 类型返回
      Bytes() []bytes 将值以字节数组 []bytes 类型返回
      String() string 将值以字符串类型返回

(1)反射取值

package main

import (
	"fmt"
	"reflect"
)

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
	case reflect.Float32:
		// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
		fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
	case reflect.Float64:
		// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
		fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
	}
}
func main() {
	var a float32 = 3.14
	var b int64 = 100
	v := reflect.ValueOf(a)
	k := v.Kind()
	fmt.Println("55555", v, k)

	reflectValue(a) // type is float32, value is 3.140000
	reflectValue(b) // type is int64, value is 100
    
	// 将int类型的 10 转换为reflect.Value类型的 10
	c := reflect.ValueOf(10)
	d := c.Kind()
	fmt.Printf("type c :%T,%v,%v\n", c, c, d) // type c :reflect.Value,10,int
}

/*
55555 3.14 float32
type is float32, value is 3.140000
type is int64, value is 100       
type c :reflect.Value,10,int
*/

(2)反射改值

  • 想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值。而反射中不用 * 取值,而是使用专有的Elem()方法来获取指针对应的值,然后通过Elem().SetInt()、Elem().SetFloat()、Elem().SetBytes()、Elem().SetString()、Elem().SetLen()、Elem().SetCap()、Elem().SetMapIndex()等方法改值
package main

import (
	"fmt"
	"reflect"
)

func reflectSetValue1(x interface{}) {
	v := reflect.ValueOf(x)
	if v.Kind() == reflect.Int64 {
		fmt.Printf("11111: %v\n", v)
		v.SetInt(200) //修改的是副本,且reflect包会引发panic
	} else {
		fmt.Println("22222:", v, v.Kind())
	}
}
func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// 反射中使用 Elem()方法获取指针对应的值
	if v.Elem().Kind() == reflect.Int64 {
		fmt.Println("33333", v.Elem())
		v.Elem().SetInt(200)  // 通过SetInt方法修改
	}
}
func main() {
	var a int64 = 100
	// reflectSetValue1(a) // 直接传值然后直接 SetInt 会报错,panic: reflect: reflect.Value.SetInt using unaddressable value
	fmt.Println(a) // 100

	reflectSetValue2(&a)  // 传入指针
	fmt.Println(a) // 200
}

3. isNil() 和 isValid()

  • IsNil()用来判断 reflect.ValueOf() 的返回的值是否为 nil 。传入reflect.ValueOf()的参数必须是 通道、函数、接口、map、指针、切片中的类型之一;否则 IsNil 函数会导致 panic

  • IsValid()用来判断 reflect.ValueOf() 的返回值是否持有指定的值。reflect.ValueOf() 的返回值是 nil 时会返回 false,返回值为 nil 时能调用的方法除了 IsValid、String、Kind之外,别的方法都会导致panic

isNil的函数定义:
func (v Value) IsNil() bool

isValid的函数定义:
func (v Value) IsValid() bool


- 示例

package main

import (
	"fmt"
	"reflect"
)

func main() {
	// *int类型空指针
	var a *int
	fmt.Println("var a *int IsNil:", reflect.ValueOf(a).IsNil())
	// nil值
	fmt.Println("nil IsValid:", reflect.ValueOf(nil).IsValid())
	// 实例化一个匿名结构体
	b := struct{}{}
	// 尝试从结构体中查找"abc"字段
	fmt.Println("不存在的结构体成员:", reflect.ValueOf(b).FieldByName("abc").IsValid())
	// 尝试从结构体中查找"abc"方法
	fmt.Println("不存在的结构体方法:", reflect.ValueOf(b).MethodByName("abc").IsValid())
	// map
	c := map[string]int{}
	// 尝试从map中查找一个不存在的键
	fmt.Println("map中不存在的键:", reflect.ValueOf(c).MapIndex(reflect.ValueOf("娜扎")).IsValid())
}

/*
var a *int IsNil: true
nil IsValid: false       
不存在的结构体成员: false
不存在的结构体方法: false
map中不存在的键: false
*/

四、结构体的反射

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

  • reflect.Type 中与获取结构体成员相关的的方法如下表所示

方法 说明
Field(i int) StructField 根据索引,返回索引对应的结构体字段的信息。
NumField() int 返回结构体成员字段数量。
FieldByName(name string) (StructField, bool) 根据给定字符串返回字符串对应的结构体字段的信息。
FieldByIndex(index []int) StructField 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息。
FieldByNameFunc(match func(string) bool) (StructField,bool) 根据传入的匹配函数匹配需要的字段。
NumMethod() int 返回该类型的方法集中方法的数目
Method(int) Method 返回该类型方法集中的第i个方法
MethodByName(string)(Method, bool) 根据方法名返回该类型方法集中的方法

1. StructField类型

  • StructField 类型用来描述结构体中的一个字段的信息

  • StructField 的定义如下:

type StructField struct {
    // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为&quot;&quot;。
    // 参见http://golang.org/ref/spec#Uniqueness_of_identifiers
    Name    string
    PkgPath string
    Type      Type      // 字段的类型
    Tag       StructTag // 字段的标签
    Offset    uintptr   // 字段在结构体中的字节偏移量
    Index     []int     // 用于Type.FieldByIndex时的索引切片
    Anonymous bool      // 是否匿名字段
}

2. 结构体反射示例

  • 遍历字段示例
  • 遍历方法示例

(1)遍历结构体内的字段

  • 当我们使用反射得到一个结构体数据之后可以通过索引依次获取其字段信息,也可以通过字段名去获取指定的字段信息
package main

import (
	"fmt"
	"reflect"
)

type student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	stu1 := student{
		Name:  "小王子",
		Score: 90,
	}

	t := reflect.TypeOf(stu1)
	fmt.Println(t.Name(), t.Kind())  // student struct
	// 通过for循环遍历结构体的所有字段信息
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("111--name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
	}

	// 通过字段名获取指定结构体字段信息
	if scoreField, ok := t.FieldByName("Score"); ok {
		fmt.Printf("222--name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
	}
}

/*
student struct
111--name:Name index:[0] type:string json tag:name
111--name:Score index:[1] type:int json tag:score 
222--name:Score index:[1] type:int json tag:score
*/

(2)遍历结构体内的方法

package main

import (
	"fmt"
	"reflect"
)

type student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

// 给student添加两个方法 Study和Sleep(注意首字母大写)
func (s student) Study() string {
	msg := "好好学习,天天向上。"
	fmt.Println(msg)
	return msg
}

func (s student) Sleep() string {
	msg := "好好睡觉,快快长大。"
	fmt.Println(msg)
	return msg
}

func printMethod(x interface{}) {
	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)

	fmt.Println("111--", t.NumMethod())
	fmt.Println("222--", v.NumMethod())
	for i := 0; i < v.NumMethod(); i++ {
		methodType := v.Method(i).Type()
		fmt.Printf("333--method name:%s\n", t.Method(i).Name)
		fmt.Printf("444--method:%s\n", methodType)
		// 通过反射调用方法传递的参数必须是 []reflect.Value 类型
		var args = []reflect.Value{}
		v.Method(i).Call(args)  // 调用当前这个方法
        fmt.Println("555")
	}
}

func main() {
	stu1 := student{
		Name:  "小王子",
		Score: 90,
	}
	printMethod(stu1)
}

/*
111-- 2
222-- 2                  
333--method name:Sleep   
444--method:func() string
好好睡觉,快快长大。     
555                      
333--method name:Study   
444--method:func() string
好好学习,天天向上。     
555 
*/

标签:反射,进阶,fmt,golang,reflect,类型,type,name
From: https://www.cnblogs.com/Mcoming/p/18061506

相关文章

  • Nestjs系列 Nestjs进阶(一)
    自定义装饰器简单使用当Nest内置的装饰器不能满足开发需求时,可以自定义装饰器,其自定义装饰器的规则和react的自定义hooks较为相似,自定义时,其封装必须使用Nest的内置装饰器或者其它自定义装饰器,react中是必须使用reacthooks,这一点规则类似。使用命令快速创建一个dec......
  • Golang使用SSE(EventSource)
    gopackagemainimport( "fmt" "gopkg.in/antage/eventsource.v1" "log" "net/http" "time")funcmain(){ es:=eventsource.New(nil,nil) deferes.Close() http.Handle("/",http.FileServe......
  • 从数据库中随机选取数据(基于golang,xorm)
    一、 从MySQL数据库中随机选取数据,可以使用SQL的 ORDERBYRAND() 语句来实现。具体步骤如下:定义一个结构体用于存储数据typeUserstruct{Idint64NamestringAgeint}建立与数据库的连接,并获取一个 Engine 实例engine,err:=xorm.NewE......
  • golang项目用k8s部署的流程
    摘要:本文将详细介绍如何使用Kubernetes(K8S)部署一套Golang微服务项目,并给出了完整的实施步骤和相应代码示例,旨在指导刚入行的开发者完成这一任务。一、整体流程下面是使用K8S部署Golang微服务项目的整体流程,我们将在接下来的内容中详细介绍每一步骤。1.准备容器镜像2.创建K8S集......
  • 【学习笔记】 - 基础数据结构 :Link-Cut Tree(进阶篇)
    前言LCT没题写可以去写树剖和一些线段树合并的题练手LCT的概念原本的树剖是对树进行剖分,剖分为重边和轻边LCT则是对于树分为虚边和实边,特殊的,LCT可以没有虚边(例:银河英雄传说v2)单独被包含在一个实链里的点称作孤立点在树剖中,我们使用线段树/树状数组来维护重链在Link-Cut......
  • 万字长文讲解Golang pprof 的使用
    往期好文推荐⭐️⭐️⭐️:#golangpprof监控系列(1)——gotrace统计原理与使用#golangpprof监控系列(2)——memory,block,mutex使用#golangpprof监控系列(3)——memory,block,mutex统计原理#golangpprof监控系列(4)——goroutinethread统计原理#golangpprof......
  • Golang 执行shell命令
    Golang执行shell命令参考:golang执行shell命令大全https://saucer-man.com/backend_development/571.html1.执行命令并获得输出结果CombinedOutput()  返回standardoutputandstandarderrorfuncmain(){cmd:=exec.Command("ls","-lah")out,err:=......
  • golang 时间比对时,time.Now()与time.Parse()的使用注意
    在11:28时执行以下代码nowTime:=time.Now()t1,err:=time.Parse("2006-01-0215:04","2024-03-0708:00:00")result:=nowTime.Before(t1)本以为result应该是false,结果竟然是true。  调试下看看两者的区别发现:time.Parse()是UTC时间,无时区信息,如:time.Time(2024-0......
  • [转]Golang atomic.CompareAndSwapInt64()实例讲解
     原文: http://www.manongjc.com/detail/30-anadyrrwgsoebxp.html-------------- 在Go语言中,原子包提供lower-level原子内存,这对实现同步算法很有帮助。Go语言中的CompareAndSwapInt64()函数用于对int64值执行比较和交换操作。此函数在原子包下定义。在这里,您需要导入“syn......
  • 第六章 面向对象进阶
    一,分包思想1、分包思想概述(理解)如果将所有的类文件都放在同一个包下,不利于管理和后期维护,所以,对于不同功能的类文件,可以放在不同的包下进行管理2、包的概述(记忆)包本质上就是文件夹创建包多级包之间使用"."进行分割多级包的定义规范:公司的网站地址翻转(去掉w......