这部分是《Go语言编程》这本书的第9章的内容。书中给该章节的定位是一个文章集,其包含了一些Go语言中比较少涉及,或是比较深入的讨论的内容。因为第一节就是反射,而反射在我看来是比较重要的内容,所以就先把这部分内容拿出来看。后续的内容可能会慢慢的补充进来。
2.1 反射
考虑以下例子:
type MyReader struct { Name string } func (r MyReader) Read (p []byte) (n int, err error) { //实现自己的Read方法 } var reader io.Reader reader = &MyReader("a.txt")
MyReader类型实现了io.Reader接口的所有方法(其实就是read函数),所以MyReader实现了接口io.Reader。
我们对接口进行反射,就可以得到一个包含Type和Value的结构。如果我们对reader进行反射,也将得到一个Type和Value,Type为io.Reader,Value为MyReader("a.txt")。
我们可以这样认为,Type主要表达的是被反射的这个变量本身的类型信息,而Value则为该变量实例本身的信息。
(总体来说我觉得这本书讲反射讲的并不多,而golang的反射又……有点……别扭。所以大概还要加点别的东西,比如:https://blog.csdn.net/fighterlyt/article/details/17360597,这篇讲的还挺清楚的。)
对于任何类型的Go语言对象,类型和值都是其运行时的相关信息,我们可以使用函数TypeOf和ValueOf来获得该对象的值信息。
func TypeOf(i interface{}) Type
func ValueOf(I interface{}) Value
Type类型是一个接口,这个接口实现了String() string方法。Value类型是一个结构体,但是并没有定义任何导出字段。Value类型同样定义了String() string方法。
那么,接下来介绍通用的类型和值所提供的方法,以及常见类型的类型和值提供的方法。这里需要注意,很多方法是由要求的,如果要求不满足的话,就会panic。
通用:
Type
func Align() int func FieldAlign() int |
对齐信息:包括做为变量时的对齐信息和作为一个结构体字段时的对齐信息。 |
func Size() uinptr | 大小:一个该类型的值所存储所需要的内存大小,以字节为单位。 |
func Name() string | 名称:该类型在其定义包中的名称,有些类型没有名称(比如数组等),将返回一个空字符串。 |
func PkgPath() string | 定义位置:该类型的定义位置,就是导入该类型使用的import语句的参数。如果该类型时预定义的(比如string,error等)或者无名的,将返回一个空字符串。 |
func Kind() Kind |
种类:该类型所属的种类。reflect包定义了Kind类型来表示各种类型。注意重命名一个类型并不会改变其种类。Kind类型定义了String() string方法。 Kind的定义包括了: const { Invalid Kind = iota Bool Int Int8 Int16 Int32 Int64 Uint Unit8 Uint16 Uint32 Uint64 Uintptr Float32 Float64 Complex64 Complex128 Array Chan Func Interface Map Ptr Slice String Struct UnsafePointer } |
func NumMethod() int | 方法集:该类型的方法集,Type类型提供了方法来返回方法数量,访问各个方法。reflect包定义了Method类型来表示一个方法。 |
func Method(index int) Method | 使用索引访问方法集。索引从0开始。如果越界则panic。 |
func MethodByName(name string) (Method, bool) | 使用名称访问方法集,bool表明是否找到该方法。 |
func Implements(u Type) bool | 判断是否实现了某接口。其中u表示一个接口类型。 |
func ConvertibleTo(u Type) bool | 判断是否可以使用标准转换语句转换为其他类型。 |
func AssignableTo(u Type) bool | 判断是否可以赋值给其他类型的变量。 |
Value
func (v Value)CanAddr() Value | 判断是否可以获得地址。如果一个值来自以下途径,那么可以获得地址:Slice的一个元素;一个可以获得地址的数组元素;一个可以获得地址的结构体的字段;解引用一个指针的结果。这个方法是反射中设置值的方法的基础。因为在使用ValueOf()生成一个Value时,参数时值传递的。因此设置这个参数的值完全没有意义。正确的方法是传入一个指针,然后调用Elem()方法来生成其指向的元素对应的Value对象。 |
func (v Value)Addr() Value | 获得地址。如果CanAddr()返回false,则会panic。 |
func (v Balue)UnsafeAddr() uintptr | 同样如果CanAddr()返回false,则会panic。 |
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。 |
func (v Value) IsValid() bool | 是否是一个合法的Value对象。这里注意,只有零值才会返回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 |
方法集和方法。这里注意,Value和Type虽然定义了同名方法,但是其返回类型是不同的。如果v没有任何方法集,或者索引越界,则会panic。MethodByName方法,如果没有找到名为name的方法,则返回零值。 |
func (v Value)String() string | 字符串格式返回。 |
func (v Value)Type() | Type 类型。 |
以上是通用的Type和Value提供的函数。
对于算术类型的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) |
设置值。所有的类型使用其对应的方法。 |
func (v Value) OverflowFloat(x float64) bool func (v Value) OverflowInt(x int64) bool func (v Value) OverflowUnit(x uint64) bool func (v Value) OverflowComplex(x complex128) bool |
辅助设置值:因为每个Set方法都对应了多个对应的具体类型,因此需要一个方法来判断设置值是否够长度。通过判断值检查是否可以存储在对象中而不溢出。 |
结构类型的Go对象
Type
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 |
该方法用于访问结构的内嵌字段。Index是一个将待访问的各个层次的字段索引排列起来的[]int。若index越界则panic。 |
Value
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(sgring) bool) Value |
访问名字使得match函数返回true的结构字段。注意:同一个内嵌层次上,只能有一个字段使得match返回true。如果同一层次上多个字段使得match返回true,那么这些字段都认为是不符合要求的。 |
func (v Value)FieldByIndex(index []int) Value |
该方法用于访问结构的内嵌字段。Index是一个将待访问的各个层次的字段索引排列起来的[]int。若index越界则panic。 |
总体来说,结构类型的Type和Value提供了几乎相同的方法,仅仅是返回值不同。
Type中,涉及了StructField类型,StructField是一个结构体,其定义如下:
Type StructField struct {
Name string
PkgPath string //对于导出字段,为空字符串;对于非导出字段,是定义该字段类型的包名
Type Type
Tag StructTag //就是结构体字段后面的那个tag
Offset uintptr //在结构体内的位移
Index []int //当使用Type.FieldByIndex()方法时的参数
Anonymous bool //是否为匿名字段
}
方法类型的Go对象:
Type
func IsVariadic() bool |
参数是否可变 |
func NumIn() int func NumOut() int |
参数和返回值的数量。可变参数单独作为slice。 |
func In(i int) Type func Out(i int) Type |
第i个参数/返回值。 |
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。 |
未完待续