Go的reflect机制和reflect包

fighterlyt · · 38655 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Goreflect机制和reflect

概述

虽然Go是静态语言,然而还是提供了reflect机制,并且定义了reflect包来辅助反射处理。在reflect包中,最重要的两个类型就是Type和Value,分别从类型、值的角度来描述一个Go对象。

Type类型是一个接口,这个接口实现了String() string方法。Value类型是一个结构,但是并未定义任何导出字段,同样定义了String() string方法。

使用如下语句来导入reflect

import "reflect"

 

注意:

1.本文中出现在类型章节的函数,都是Type的方法。由于接口的特殊性,无法明确其receiver是指针还是值,所以并未显示其receiver,但都是有receiver

2.很多方法都有要求,如果要求不满足,那么panic

3.Value类型也有零值,零值只能调用IsValid()/String()/Kind()方法,其余方法都会panic

 

下面我们将依次介绍不同类型的对象和它对应的Type/Value对象

Goreflect机制和reflect 1

类型 3

值 4

算术类型的Go对象: 6

Goreflect机制和reflect 1

类型 3

Kind类型 4

值 4

算术类型的Go对象: 6

类型 6

值 6

结构类型的Go对象 7

类型 7

StructField类型 7

StructTag类型 8

值 8

方法类型的Go对象 8

类型 8

值 9

通道类型的Go对象 9

类型 9

: 10

Slice类型的Go对象 10

类型 11

值 11

映射类型的Go对象 11

类型 11

值 12

指针类型的Go对象 12

类型 12

值 12

数组类型的Go对象 12

类型 12

值 12

接口类型的Go对象 13

值 13

[]byte类型的Go对象 13

值 13

字符串类型的Go对象 13

值 13

组合生成一个Value 13

组合生成一个Type 15

 

值 6

结构类型的Go对象 7

类型 7

值 8

方法类型的Go对象 8

类型 8

值 9

通道类型的Go对象 9

类型 9

: 10

Slice类型的Go对象 10

类型 11

值 11

映射类型的Go对象 11

类型 11

值 12

指针类型的Go对象 12

类型 12

值 12

数组类型的Go对象 12

类型 12

值 12

接口类型的Go对象 13

值 13

[]byte类型的Go对象 13

值 13

字符串类型的Go对象 13

值 13

组合生成一个Value 13

组合生成一个Type 15

 

所有类型的Go对象:

对于任何类型的Go对象而言,类型和值都是其运行时的相关信息,使用TypeOf()/ValueOf()函数获得表示类型/值信息。

func TypeOf(i interface{}) Type

func ValueOf(i interface{}) Value

类型

Type类型定义了方法来提供如下:

* 对齐信息: 包括作为变量时的对齐要求和作为一个结构字段时的对齐信息

func Align() int

func FieldAlign() int

* 大小: 一个该类型的值存储所需要的内存大小,以字节为单位

func Size() uinptr

* 名称: 该类型在其定义包中的名称,有些类型没有名称(比如数组、slice、函数等等),返回一个空字符串

func Name() string

* 定义位置: 该类型的定义位置,也就是导入该类型使用的import语句的参数。如果该类型是预定义的(string, error)或者无名的,那么返回一个空字符串

func PkgPath() string

* 种类: 该类型所属的种类,reflect包定义了Kind类型来表示各种类型。重命名一个类型并不会改变其种类

func Kind() Kind

* 方法集: 该类型的方法集,Type类型提供了方法来返回方法数量,访问各个方法。reflect包定义了Method类型来表示一个方法

func NumMethod() int

func Method(index int) Method

//使用索引来访问方法集,索引从0开始,如果越界,将panic

func MethodByName(name string) (Method, bool)

//使用名称来访问某个方法,bool参数指示是否找到该方法

* 是否实现某个接口

func Implements( u Type) bool

//判断是否使用了接口u,u必须表示一个接口类型

* 是否可以使用标准转换语句,转换为其他类型

func ConvertibleTo(u Type) bool

* 是否可以赋值给其他类型的变量

func AssignableTo(u Type) bool

Kind类型

reflect包使用Kind类型来表示类型所属的分类,其定义如下:

type Kind uint

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

)

Kind()类型定义了String() string方法,提供一个字符串表示形式

 

Value类型表示一个Go对象的值,对于任意类型的Go对象,以下方法都是可用的

* 是否可以获得地址。如果一个值来自以下途径,那么可以获得其地址

l Slice的一个元素

l 一个可以获得地址的数组的元素

l 一个可以获得地址的结构的字段

l 解引用一个指针的结果

func (v Value) CanAddr() Value

//这个方法是设置值的方法的基础,使用ValueOf()生成一个Value时,参数是值传递的,因此设置这个参数一点意义也没有。正确的方法是传入一个指针,然后调用Elem()方法来生成其指向的元素对应的Value对象

* 获得其地址

func (v Value) Addr() Value

//如果CanAddr()返回false,那么这个调用会panic

func (v Value) UnsafeAddr() uintptr

//Addr()方法有同样的要求

 

* 是否可以修改其值,一个值必须是可以获得地址且不能通过访问结构的非导出字段获得,方可被修改

func (v Value) CanSet() bool

* 设置值

func (v Value) Set(x Value)

//如果CanSet()返回false,那么panic

* 转换为其他类型的值

func (v Value) Convert(t Type) Value

//如果无法使用标准Go转换规则来转换,那么panic

* 以空接口类型获得值

func (v Value) Iterface{} interface{}

//如果Value是通过访问结构的非导出字段获得,panic

* 是否是一个合法的Value对象

func (v Value) IsValid() bool

//只有零值才会返回false

* 所属的类型分类

func (v Value) Kind() Kind

//零值会返回Invalid

* 方法集和方法

func (v Value) NumMethod() int

func (v Value) Method(index int) Value

func (v Value) MethodByName(name string) Value

//Type类型定义了同名方法,但是返回的是类型信息,这里返回的是值信息。Method()方法,如果v没有任何方法集,或者index越界,那么panicMethodByName()方法,如果没有找到名为name的方法,那么返回一个零值

* 字符串格式

func (v Value) String() string

//返回值的格式为<类型 值>

* 类型

func (v Value) Type() Type

算术类型的Go对象:

 

类型

对于算术类型,Type类型提供了以下方法

* 位数: 该类型大小,以二进制位为单位

func Bits() int

对于算术类型,Value类型特别提供了如下方法来方便处理

* 获得值

func (v Value) Float() float64

//所有的浮点数类型都是使用这个方法

func (v Value) Int() int64

//所有的有符号整数类型,都是使用这个方法

func (v value) Unt() uint64

//所有的无符号整数类型,都是使用这个方法

func (v Value) Complex() complex128

//所有的复数类型,都是使用这个方法

* 设置值:

func (v Value) SetFloat(x float64)

//所有的浮点数类型都是使用这个方法

func (v Value) SetInt(x int64)

//所有的有符号整数类型,都是使用这个方法

func (v value) SetUnt(x uint64)

//所有的无符号整数类型,都是使用这个方法

func (v Value) SetComplex(x complex128)

//所有的复数类型,都是使用这个方法

* 辅助设置值:由于每个Setxxx方法都是对应了多个具体的基本类型,因此需要一个方法来判断设置值是否能够长度,通过判断值是否可以存储在Valud对象中且不溢出

func (v Value) OverflowFloat(x float64) bool

//所有的浮点数类型都是使用这个方法

func (v Value) OverflowInt(x int64) bool

//所有的有符号整数类型,都是使用这个方法

func (v value) OverflowUnt(x uint64) bool

//所有的无符号整数类型,都是使用这个方法

func (v Value) OverflowComplex(x complex128) bool

//所有的复数类型,都是使用这个方法

//如果使用某个导致溢出的值来调用Setxxx()方法

结构类型Go对象

类型

结构对象的类型就是字段的数量和类型,但结构的字段具有很多特殊信息,reflect包定义了StructField类型来表示一个字段

func NumField() int

//结构字段数量

func Field(i int) StructField

//使用索引来访问字段,索引从0开始,如果越界将panic

func FieldByName(name string) (StructField,bool)

//使用名称来访问字段,如果未找到那么返回false

func FieldByNameFunc(match func(string) bool) (StructField,bool)

//访问名称使得match函数返回true的字段,在同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true,那么这些字段都认为是不符合要求的

func FieldByIndex(index []int) StructField

//这个方法使得访问结构的内嵌字段成为可能。将访问各个层次的字段的索引排列起来,就形成了一个[]int,参数index不可越界,否则panic

StructField类型

StructField是一个结构,其定义和含义如下:

type StructField struct{

    Name string      //名称

    PkgPath string

    Type        Type

    Tag         StructTag

    Offset uintptr     //在结构内的位移

    Index []int       //当使用Type.FieldByIndex()方法的参数

    Anonymous  bool        //是否为匿名字段

}

PkgPath字段需要做一些解释:

* 对于导出字段,为空字符串

* 对于非导出字段,是定义该字段类型的包名

StructTag类型

StructTag类型也是一个结构,描述了结构字段的tag。按照约定,tag格式为:

* 由多个部分连接而成,部分之间有可选的空格

* 部分格式为 key:value

* key是非空的字符串,由非控制字符组成,并且不可以是空格、双引号、冒号

* 值由双引号包围,遵循Go字符串字面值语法

定义如下:

type StructTag string

func (tag StructTag) Get(key string) string

//将一个tag看做映射,各个部分就是映射的元素

 

Value类型为结构类型的Go对象几乎定义了相同的方法,唯一的不同是这些方法返回的是一个Value,而不是StructFieldValue类型有零值,因此返回bool值就没有意义了

func (v Value) NumField() int

//结构字段数量

func (v Value) Field(i int) Value

//使用索引来访问字段,索引从0开始,如果越界将panic

func (v Value) FieldByName(name string) Value

//使用名称来访问字段,如果未找到那么返回false

func (v Value)FieldByNameFunc(match func(string) bool) Value

//访问名称使得match函数返回true的字段,在同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true,那么这些字段都认为是不符合要求的

func FieldByIndex(index []int) Value

//这个方法使得访问结构的内嵌字段成为可能。将访问各个层次的字段的索引排列起来,就形成了一个[]int,参数index不可越界,否则panic

方法类型的Go对象

类型

对于一个函数类型,所有的信息都在其签名中,那就是参数的数量、类型以及返回值的数量、类型。

Type类型定义了如下方法,提供这些信息

func IsVariadic() bool

//参数是否可变

func NumIn() int

func NumOut() int

//参数、返回值的数量,需要注意的是,可变参数单独作为slice计算

func In(i int) Type

func Out(i int) Type

//i个参数/返回值,i0开始

对于一个函数类型值,Value类型提供了2个方法来调用这个值,也就是调用函数。

reflect包中,Value类型被定义为一个结构,并且没有任何的导出字段,所有的信息都是通过方法调用给出的。

func (v Value) Call(in []Value)[]Value

func (v Value) CallSlice(in []Value) []Value

Call()方法用来调用函数(参数可变或者固定),采用的是用户代码使用的调用格式。CallSlice()方法专门用于调用参数可变的函数,它采用了编译器使用的调用格式。这两种调用格式的区别在于:

u 对于参数固定的函数,两种格式没有任何区别,都是按照位置,将实参赋予形参

u 对于参数可变的函数,编译器格式会特别处理最后一个参数,将剩余的实参依次放入一个slice内,传递给可变形参的就是这个slice

 

还有一个比较特殊的方法

func (v Value) Pointer() uintptr

//uintptr返回函数的值,这个值并不能独一无二的识别一个函数,只是保证如果函数为nil,那么这个值为0

 

 

通道类型的Go对象

 

Go语言的通道对象信息可以分为两类:

l 静态信息: 通道的方向和元素类型

l 动态信息: 发送、接收,通道的容量和当前元素数量

类型

Value对象定义了了以下方法来获得通道类型的类型信息

func ChanDir() ChanDir

//判断通道的方向

func Elem() Type

//元素的类型

ChanDir

ChanDir表示通道的方向,其定义如下

type ChanDir int

func (chan ChanDir)String() string

reflect包定义了一组常量来表示各种方向

const (

RecvDir ChanDir =1<<iota

SendDir 

BothDir

)

:

对于一个通道值,Value定义了如下方法

func (v Value) IsNil() bool

func (v Value) Pointer() uintptr

//unitptr返回其值,没有使用unsafe.Pointer类型,所以不需要导入unsafe

func (v Value) Close()

func (v Value) Len() int

//通道当前元素数量

func (v Value) Cap() int

//通道的长度

func (v Value) Send(x Value) 

//发送一个值,x必表示一个可以赋值给通道元素类型的值

func (v Value) TrySend(x Value) bool

//尝试以非阻塞的方式发送一个值,返回操作成功与否

func (v Value) Recv() (Value,bool)

//接收一个值,如果通道已经关闭,返回一个Value零值。由于通道本身可能传输Value零值,所以必须额外使用一个布尔返回值来表示接收是否成功

func (v Value) TryRecv() (Value,bool)

//尝试以非阻塞的方式接收一个值

 

 

Slice类型的Go对象

1.2版本中,每个Slice的真实类型为

type SliceHeader struct {

Data uintptr

Len int

Cap int

}

在官方文档中这样写明:这个格式可能会在后续的版本中改变。Data字段并不足够确保它引用的数据不会被回收,程序应该以正确的格式来另外保存一个指向底层数据的指针

 

Go语言的slice对象信息可以分为两类:

n 静态信息: 元素类型

n 动态信息: 长度和容量

类型

func Elem() Type

func (v Value) Len() int

func (v Value) Cap() int

func (v Value) IsNil() bool

func (v Value) Pointer() uintptr

func (v Value) Index(i int) Value

//访问某个元素

func (v Value) Slice(i,j int) Value

//访问某个子slice,下标必须合法

func (v Value) Slice3(i,j,k) Value

//Go1.3引入的3下标格式访问某个子slice,下标必须合法

func (v Value) SetCap(i int)

//要求i必须在[v.Len(),v.Cap()]之间

func (v Value) SetLen(i int)

//i必须在[0,v.Cap()]之间

映射类型的Go对象

映射类型的Go对象信息可以分为两类:

l 静态信息: 键类型和元素类型

l 动态信息: 键值、元素值以及映射的大小

类型

func Key() Type

func Elem() Type

func (v Value) Len() int

func (v Value) IsNil() bool

func (v Value) MapKeys() []Value

//返回所有的键值

func (v Value) MapIndex(key Value) Value

func (v Value) SetMapIndex(key, x Value)

//如果x是零值,那么表示删除一个元素

func (v Value) Pointer() uintptr

指针类型的Go对象

Go语言提供了两种指针类型,一种是通过*和其他类型复合而成,另一种是unsafe.Pointer

类型

func Elem() Type

func(v Value) IsNil() bool

func(v Value) Elem() Value

func(v Value) Pointer() uintptr

数组类型的Go对象

类型

func Elem() Type

func Len() int

func(v Value) Len() int

func(v Value) Slice(i,j int) Value

func(v Value) Slice3(i, j, k int) Value

//这两个方法要求v.CanAddr()返回true

接口类型的Go对象

func(v Value) IsNil() bool

//判断接口是否为空

func(v Value) Elem() Value

//返回接口包含的真实值

func(v Value) InterfaceData() [2]uintptr

//这个方法的用法比较奇怪,还未能找到一个合适的例子

[]byte类型的Go对象

[]byte类型应用非常广泛,几乎所有涉及到数据的操作都会使用这个类型。Value类型特别为这个类型定义了相关操作

func (v Value)Bytes() []bytes

func (v Value)SetBytes(x []bytes)

 

字符串类型的Go对象

func (v Value) SetString(x string)

//设置字符串的值

func (v Value) Index(i int)Value

//访问单个字节

func (v Value) Len() int

//字符串的长度

 

 

组合生成一个Value

当我们已有某个Value值,可以使用以下方法来组合生成新的Value对象

func Append(s Value,value ...Value) Value

func AppendSlice(s, t Value) Value

* 前者将value参数附加到s参数中,返回结果

* 后者将t参数的元素附加到s参数末尾,返回结果

* 很显然,以值传递参数,参数本身的值并未改变

* s,t 必须都是表示slice,并且遵循Goslice规则,即元素类型必须可赋值(前者)/相同(后者)

 

func Indirect(s Value) Value

* 如果s表示一个指针,那么返回所指向的元素,特别地,nil指针返回一个零值的Value

* 如果s表示其他类型值,那么返回s本身

 

或者使用一个Type类型,生成表示该类型值的Value,当然生成的都是零值

func MakeChan(typ Type, buffer int) Value

生成一个通道

func MakeMap(typ Type) Value

生成一个映射

func MakeSlice(typ Type, len, cap int) Value

生成一个slice

func New(typ Type) Value

生成一个类型为typ的零值,返回的是其指针,符合New的语义

func NewAt(typ Type, p unsafe.Pointer) Value

p当做一个指向typ类型的指针,包装并返回

func Zero(typ Type) Value

生成typ类型的零值,这里其实是返回特殊值,返回值既不可以修改也不可以取得其地址

func MakeFunc(typ Type, fn func(args []Value)(results []Value)) Value

从某个函数类型typ生成一个Value,返回值wrap了第二个参数。返回值的类型为typ,但真实操作却由fn完成。返回值在调用时,会发生以下操作:

1.将所有参数转换为Value,并且加入到fnargs中。特别地,可变参数会先转换为一个slice,然后作为args的最后一个参数加入。

2.调用fn

3.将fn的返回值转换为typ中描述的类型,并返回

fn的签名类型非常特殊,允许任何数量、类型的参数,返回任何数量、类型的返回值,因此任何函数都可以包装在这样的签名里。真实的签名由typ提供,从而提供了动态生成函数的机制。

go文档提供了一个很好的例子,但这个函数的应用非常之广泛,就靠大家来探索了。

组合生成一个Type

当我们已经有一个Type值,可以使用以下方法来组合生成一个新的Type对象

func SliceOf(t Type) Type

func ChanOf(dir ChanDir, t Type) Type

func MapOf(key, value Type) Type

func PtrTo(t Type) Type

这些函数都非常简单,不需要什么特殊说明


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:fighterlyt

查看原文:Go的reflect机制和reflect包

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

38655 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传