Golang 学习笔记(11)—— 反射

介绍

反射是程序执行时检查其所拥有的结构。尤其是类型的一种能力。这是元编程的一种形式。它同一时候也是造成混淆的重要来源。

每一个语言的反射模型都不同(同一时候很多语言根本不支持反射)。本节将试图明白解释在 Go 中的反射是怎样工作的

实现

package

import "reflect" Type和Value

首先,reflect包有两个数据类型我们必须知道,一个是Type,一个是Value。

Type就是定义的类型的一个数据类型,Value是值的类型

下面分别对Type和Value做个简单介绍一下

Type

Type类型用来表示一个go类型。

不是所有go类型的Type值都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知类型的分类。调用该分类不支持的方法会导致运行时的panic。

获取Type对象的方法:

func TypeOf(i interface{}) Type 例如:

str := "this is string" type := reflect.TypeOf(str) Type中常用的方法:

Kind() Kind Kind返回该接口的具体分类 Kind代表Type类型值表示的具体分类。零值表示非法分类。 const ( Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Uint8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer ) Name() string Name返回该类型在自身包内的类型名,如果是未命名类型会返回""

PkgPath() string PkgPath返回类型的包路径,即明确指定包的import路径,如"encoding/base64" 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""

NumField() int 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic

Field(i int) StructField 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic

FieldByIndex(index []int) StructField 返回索引序列指定的嵌套字段的类型, 等价于用索引中每个值链式调用本方法,如非结构体将会panic

FieldByName(name string) (StructField, bool) 返回该类型名为name的字段(会查找匿名字段及其子字段), 布尔值说明是否找到,如非结构体将panic

type StructField

type StructField struct { // Name是字段的名字。PkgPath是非导出字段的包路径,对导出字段该字段为""。 Name string PkgPath string Type Type // 字段的类型 Tag StructTag // 字段的标签 Offset uintptr // 字段在结构体中的字节偏移量 Index []int // 用于Type.FieldByIndex时的索引切片 Anonymous bool // 是否匿名字段 } StructField类型描述结构体中的一个字段的信息。

NumMethod() int 返回该类型的方法集中方法的数目 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法; 匿名字段导致的歧义方法会滤除

Method(int) Method 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil

MethodByName(string) (Method, bool) 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil

type Method

type Method struct { // Name是方法名。PkgPath是非导出字段的包路径,对导出字段该字段为""。 // 结合PkgPath和Name可以从方法集中指定一个方法。 Name string PkgPath string Type Type // 方法类型 Func Value // 方法的值 Index int // 用于Type.Method的索引 } Method代表一个方法。

Value

Value为go值提供了反射接口。

不是所有go类型值的Value表示都能使用所有方法。请参见每个方法的文档获取使用限制。在调用有分类限定的方法时,应先使用Kind方法获知该值的分类。调用该分类不支持的方法会导致运行时的panic。

Value类型的零值表示不持有某个值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回,所有其它方法都会panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

如果某个go类型值可以安全的用于多线程并发操作,它的Value表示也可以安全的用于并发。

获取Value对象的方法:

func ValueOf(i interface{}) Value ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。

例如:

str := "this is string" val := reflect.ValueOf(str) ** Value中常用的方法:**

Kind() func (v Value) Kind() Kind Kind返回v持有的值的分类,如果v是Value零值,返回值为Invalid

func (Value) IsValid func (v Value) IsValid() bool IsValid返回v是否持有一个值。如果v是Value零值会返回假,此时v除了IsValid、String、Kind之外的方法都会导致panic。绝大多数函数和方法都永远不返回Value零值。如果某个函数/方法返回了非法的Value,它的文档必须显式的说明具体情况。

func (Value) IsNil func (v Value) IsNil() bool IsNil报告v持有的值是否为nil。v持有的值的分类必须是通道、函数、接口、映射、指针、切片之一;否则IsNil函数会导致panic。注意IsNil并不总是等价于go语言中值与nil的常规比较。例如:如果v是通过使用某个值为nil的接口调用ValueOf函数创建的,v.IsNil()返回真,但是如果v是Value零值,会panic。

func (Value) Type func (v Value) Type() Type 返回v持有的值的类型的Type表示。

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

func (Value) NumField func (v Value) NumField() int 返回v持有的结构体类型值的字段数,如果v的Kind不是Struct会panic

func (Value) Field func (v Value) Field(i int) Value 返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic

func (Value) FieldByIndex func (v Value) FieldByIndex(index []int) Value 返回索引序列指定的嵌套字段的Value表示,等价于用索引中的值链式调用本方法,如v的Kind非Struct将会panic

func (Value) FieldByName func (v Value) FieldByName(name string) Value 返回该类型名为name的字段(的Value封装)(会查找匿名字段及其子字段),如果v的Kind不是Struct会panic;如果未找到会返回Value零值。

func (Value) NumMethod func (v Value) NumMethod() int 返回v持有值的方法集的方法数目。

func (Value) Method func (v Value) Method(i int) Value 返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果i出界,或者v的持有值是接口类型的零值(nil),会panic。

func (Value) MethodByName func (v Value) MethodByName(name string) Value 返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装。返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。如果未找到该方法,会返回一个Value零值。

func (Value) Call func (v Value) Call(in []Value) []Value Call方法使用输入的参数in调用v持有的函数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。如果v持有值是可变参数函数,Call方法会自行创建一个代表可变参数的切片,将对应可变参数的值都拷贝到里面。

func (Value) CallSlice func (v Value) CallSlice(in []Value) []Value CallSlice调用v持有的可变参数函数,会将切片类型的in[len(in)-1](的成员)分配给v的最后的可变参数。例如,如果len(in) == 3,v.Call(in)代表调用v(in[0], in[1], in[2])(其中Value值表示其持有值,可变参数函数的可变参数位置提供一个切片并跟三个点号代表"解切片")。如果v的Kind不是Func或者v的持有值不是可变参数函数,会panic。它返回函数所有输出结果的Value封装的切片。和go代码一样,每一个输入实参的持有值都必须可以直接赋值给函数对应输入参数的类型。

func (Value) Interface func (v Value) Interface() (i interface{}) 本方法返回v当前持有的值(表示为/保管在interface{}类型),等价于:

var i interface{} = (v's underlying value) 如果v是通过访问非导出结构体字段获取的,会导致panic。 另外还有很多类似这个函数的功能可以获取value的值。

func (v Value) String() string func (v Value) Bool() bool func (v Value) Int() int64 func (v Value) Uint() uint64 func (v Value) Float() float64 func (v Value) Complex() complex128 func (v Value) Bytes() []byte ... func (Value) CanSet func (v Value) CanSet() bool 如果v持有的值可以被修改,CanSet就会返回真。只有一个Value持有值可以被寻址同时又不是来自非导出字段时,它才可以被修改。如果CanSet返回假,调用Set或任何限定类型的设置函数(如SetBool、SetInt64)都会panic。

设置函数 func (v Value) SetBool(x bool) func (v Value) SetInt(x int64) func (v Value) SetUint(x uint64) func (v Value) SetFloat(x float64) func (v Value) SetComplex(x complex128) func (v Value) SetBytes(x []byte) func (v Value) SetString(x string) ... 例子

说了这么多,看下实例吧 对象:

package demo

import ( "fmt" )

type Address struct{ City string Area string }

type Student struct{ Address Name string Age int }

func (this Student) Say(){ fmt.Println("hello, i am ", this.Name, "and i am ", this.Age) }

func (this Student) Hello(word string){ fmt.Println("hello", word, ". i am ", this.Name) } 有关反射函数demo

package demo

import ( "fmt" "reflect" )

/* 获取对象的信息 */ func StructInfo(o interface{}){ //获取对象的类型 t := reflect.TypeOf(o) fmt.Println(t.Name(), "object type: ", t.Name())

if k := t.Kind(); k != reflect.Struct{
    fmt.Println("the object is not a struct, but it is", t.Kind())
    return
}

//获取对象的值
v := reflect.ValueOf(o)
fmt.Println(t.Name(), "object value: ", v)

//获取对象的字段
fmt.Println(t.Name(), "fields: ")
for i := 0; i < t.NumField(); i++{
    f := t.Field(i)
    val := v.Field(i).Interface()
    fmt.Printf("%6s:%v = %v \n", f.Name, f.Type, val)
    //通过递归调用获取子类型的信息
    t1 := reflect.TypeOf(val)
    if k := t1.Kind(); k == reflect.Struct{
        StructInfo(val)
    }
}
//获取对象的函数
fmt.Println(t.Name(), "methods: ", t.NumMethod())
for i := 0; i < t.NumMethod(); i++{
    m := t.Method(i)
    fmt.Printf("%10s:%v \n", m.Name, m.Type)
}
复制代码

}

/* 匿名字段的反射 */ func Annoy(o interface{}){ t := reflect.TypeOf(o) for i := 0; i < t.NumField(); i++{ f := t.Field(i) fmt.Printf("%10s:%#v \n", f.Name, f) } }

/* 通过反射设置字段 */ func ReflectSet(o interface{}){ v := reflect.ValueOf(o) if v.Kind() == reflect.Ptr && !v.Elem().CanSet(){ fmt.Println("修改失败") return } v = v.Elem() //获取字段 f := v.FieldByName("Name") if !f.IsValid(){ fmt.Println("修改失败") return } //设置值 if f.Kind() == reflect.String{ f.SetString("chairis") } }

/* 通过反射调用函数 */ func ReflectMethod(o interface{}){ v := reflect.ValueOf(o) //无参函数调用 m1:= v.MethodByName("Say") m1.Call([]reflect.Value{})

//有参函数调用
m2 := v.MethodByName("Hello")
m2.Call([]reflect.Value{reflect.ValueOf("iris")})
复制代码

} 上述代码一共有以下这几个函数: StructInfo:是用来说明type, 和value的使用方式的按照以下方式调用:

package main

import ( . "stu_demo/demo" )

func main(){ stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23} StructInfo(stu) } 结果如下:

运行结果 Annoy : 用来展示匿名字段的函数。 照以下方式调用:

package main

import ( . "stu_demo/demo" )

func main(){ stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23} Annoy(stu) } 结果如下:

运行结果 ReflectSet : 是用来实现通过反射来修改字段值的函数。 按照以下调用:

package main

import ( "fmt" . "stu_demo/demo" )

func main(){ stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23} fmt.Println("before: ", stu) ReflectSet(&stu) fmt.Println("after: ", stu) } 结果如下:

运行结果 ReflectMethod : 是用来实现通过反射调用函数的方法。 按照以下调用:

package main

import ( . "stu_demo/demo" )

func main(){ stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23} ReflectMethod(stu) } 得以下结果:

运行结果 源码

作者:ChainZhang 链接:https://www.jianshu.com/p/53adb1e92710 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://juejin.im/post/5a6d9912518825732c53c9ab

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GoLang学习笔记主要包括以下几个方面: 1. 语法规则:Go语言要求按照语法规则编写代码,例如变量声明、函数定义、控制结构等。如果程序中违反了语法规则,编译器会报错。 2. 注释:Go语言中的注释有两种形式,分别是行注释和块注释。行注释使用`//`开头,块注释使用`/*`开头,`*/`结尾。注释可以提高代码的可读性。 3. 规范代码的使用:包括正确的缩进和空白、注释风格、运算符两边加空格等。同时,Go语言的代码风格推荐使用行注释进行注释整个方法和语句。 4. 常用数据结构:如数组、切片、字符串、映射(map)等。可以使用for range遍历这些数据结构。 5. 循环结构:Go语言支持常见的循环结构,如for循环、while循环等。 6. 函数:Go语言中的函数使用`func`关键字定义,可以有参数和返回值。函数可以提高代码的重用性。 7. 指针:Go语言中的指针是一种特殊的变量,它存储的是另一个变量的内存地址。指针可以实现动态内存分配和引用类型。 8. 并发编程:Go语言提供了goroutine和channel两个并发编程的基本单位,可以方便地实现多线程和高并发程序。 9. 标准库:Go语言提供了丰富的标准库,涵盖了网络编程、文件操作、加密解密等多个领域,可以帮助开发者快速实现各种功能。 10. 错误处理:Go语言中的错误处理使用`defer`和`panic`两个关键字实现,可以有效地处理程序运行过程中出现的错误。 通过以上内容的学习,可以掌握Go语言的基本语法和编程思想,为进一步学习和应用Go语言打下坚实的基础。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Golang学习笔记](https://blog.csdn.net/weixin_52310067/article/details/129467041)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [golang学习笔记](https://blog.csdn.net/qq_44336275/article/details/111143767)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值