当前位置: 首页 > news >正文

福建住房和建设网站/武汉官网优化公司

福建住房和建设网站,武汉官网优化公司,快速判断网站开发语言,广州推动优化防控措施落地15.反射(1)0. types 和 interface1. 基本概念1.1 基本数据结构和入口函数1.2 基础类型1.3类型汇总参考资料何为反射? 反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说…

15.反射(1)

  • 0. types 和 interface
  • 1. 基本概念
    • 1.1 基本数据结构和入口函数
    • 1.2 基础类型
    • 1.3类型汇总
  • 参考资料

何为反射? 反射是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

不用反射就不能在运行时访问、检测和修改它本身的状态和行为吗?首先理解什么叫访问、检测和修改它本身状态或行为,它的本质是什么。实际上,它的本质是程序在运行期探知对象的类型信息和内存结构,不用反射能行吗?可以的!使用汇编语言,直接和内层打交道,可以获取任意信息。但是,当编程迁移到高级语言上来之后,就不行了,只能通过反射来达到此项技能。

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。Go语言反射的基础是编译器和运行时把类型信息以合适的数据结构保存在可执行程序中。

需要反射的 2 个常见场景:

  • 有时需要编写一个函数,但是并不知道传递过来的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  • 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

0. types 和 interface

Go 语言中每个变量都有一个静态类型,在编译阶段就确定了的,比如 int, float64, []int 等等。注意,这个类型是声明时候的类型,不是底层数据类型。例如:

type MyInt intvar i int
var j MyInt

尽管 i,j 的底层类型都是 int,但是他们是不同的静态类型,除非进行类型转换,否则,i 和 j 不能同时出现在等号两侧。j 的静态类型就是 MyInt。

反射主要与 interface{}类型相关。之前的博客【Go语言实战】7.接口已经探讨过 interface 的底层结构,这里再复习一下。

type iface struct {tab  *itabdata unsafe.Pointer
}type itab struct {inter  *interfacetype_type  *_typelink   *itabhash   uint32bad    boolinhash boolunused [2]bytefun    [1]uintptr
}

其中 itab 由具体类型 _type 以及 interfacetype 组成。_type 表示具体类型,而 interfacetype 则表示具体类型实现的接口类型。
iface和itab的关系
实际上,iface 描述的是非空接口,它包含方法与之相对的是 eface,描述的是空接口,不包含任何方法,Go 语言里有的类型都 “实现了” 空接口

type eface struct {_type *_typedata  unsafe.Pointer
}

相比 iface,eface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值。
实例代码如下:

package mainimport ("fmt""io""os""unsafe"
)type iface struct {tab  *itabdata unsafe.Pointer
}
type itab struct {inter uintptr_type uintptrlink  uintptrhash  uint32_     [4]bytefun   [1]uintptr
}
type eface struct {_type uintptrdata  unsafe.Pointer
}func main() {var r io.Readerfmt.Printf("initial r: %T, %v\n", r, r)tty, _ := os.OpenFile("D:\\Go_WorkSpaces\\LearnGo\\error.txt", os.O_RDWR, 0)fmt.Printf("tty: %T, %v\n", tty, tty)//给r赋值r = ttyfmt.Printf("r: %T, %v\n", r, r)rIface := (*iface)(unsafe.Pointer(&r))fmt.Printf("r: iface.tab._type = %#x, iface.data = %#x\n", rIface.tab._type, rIface.data)// 给 w 赋值var w io.Writerw = r.(io.Writer)fmt.Printf("w: %T, %v\n", w, w)wIface := (*iface)(unsafe.Pointer(&w))fmt.Printf("w: iface.tab._type = %#x, iface.data = %#x\n", wIface.tab._type, wIface.data)// 给 empty 赋值var empty interface{}empty = wfmt.Printf("empty: %T, %v\n", empty, empty)emptyEface := (*eface)(unsafe.Pointer(&empty))fmt.Printf("empty: eface._type = %#x, eface.data = %#x\n", emptyEface._type, emptyEface.data)
}

其执行结果为:

initial r: <nil>, <nil>
tty: *os.File, &{0xc000080780}
r: *os.File, &{0xc000080780}
r: iface.tab._type = 0xd4af20, iface.data = 0xc000006030
w: *os.File, &{0xc000080780}
w: iface.tab._type = 0xd4af20, iface.data = 0xc000006030
empty: *os.File, &{0xc000080780}
empty: eface._type = 0xd4af20, eface.data = 0xc000006030

r,w,empty 的动态类型和动态值都一样。

1. 基本概念

Go的反射基础是接口和类型系统。Go的反射巧妙地借助了实例到接口的转换所使用的数据结构,首先将实例传递给内部的空接口,实际上是将一个实例类型转换为接口可以表述的数据结构eface,反射基于这个转换后的数据结构来访问和操作实例的值和类型。

Go 语言在reflect包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

1.1 基本数据结构和入口函数

**反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。**一个 Type 表示一个Go类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。唯一能反映 reflect.Type 实现的是接口的类型描述信息,也正是这个实体标识了接口值的动态类型。

reflect.Type
反射包里面有一个通用的描述类型公共信息的结构rType,这个rType实际上和内部接口实现时的runtime包里面的_type是同一个东西,只是因为包的隔离性分开定义而已,都是描述类型的通用信息,同时为每一种基础类型封装了一个特定的结构。源码如下:

// rtype is the common implementation of most values. It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {size       uintptrptrdata    uintptr // number of bytes in the type that can contain pointershash       uint32  // hash of type; avoids computation in hash tablestflag      tflag   // extra type information flagsalign      uint8   // alignment of variable with this typefieldAlign uint8   // alignment of struct field with this typekind       uint8   // enumeration for C// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal     func(unsafe.Pointer, unsafe.Pointer) boolgcdata    *byte   // garbage collection datastr       nameOff // string formptrToThis typeOff // type for pointer to this type, may be zero
}
// arrayType represents a fixed array type.
type arrayType struct {rtypeelem  *rtype // array element typeslice *rtype // slice typelen   uintptr
}// chanType represents a channel type.
type chanType struct {rtypeelem *rtype  // channel element typedir  uintptr // channel direction (ChanDir)
}
// ptrType represents a pointer type.
type ptrType struct {rtypeelem *rtype // pointer element (pointed at) type
}// sliceType represents a slice type.
type sliceType struct {rtypeelem *rtype // slice element type
}

rType实现了接口reflect.Type. Go的reflect包通过函数relfecct.TypeOf()返回一个Type类型的接口,使用者通过接口来获取对象的类型信息。

因为 reflect.TypeOf 返回的是一个动态类型的接口值,它总是返回具体的类型。因此,下面的代码将打印 “*os.File” 而不是 “io.Writer”。稍后,我们将看到能够表达接口类型的 reflect.Type。

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"

要注意的是 reflect.Type 接口是满足 fmt.Stringer 接口的。因为打印一个接口的动态类型对于调试和日志是有帮助的, fmt.Printf 提供了一个缩写 %T 参数,内部使用 reflect.TypeOf 来输出:

fmt.Printf("%T\n", 3) // "int"

为什么反射接口返回的是一个Type接口类型,而不是直接返回rType?原因很简单:一是因为类型信息是一个只读的信息,不可能动态地修改类型的相关信息,这是不安全的;二是因为不同对的类型,类型定义也不一样,使用接口这一抽象数据结构能够进行统一的抽象。所以relfect包通过reflect.TypeOf()函数返回一个Type的接口变量,通过接口抽象出来的方法访问具体类型的信息。

reflect.TypeOf()的函数原型如下:

func TypeOf(i interface{}) Type

形参是一个空接口类型,返回值是一个Type接口类型。
下面介绍reflect.Type接口的主要方法:
(1)所有类型通用的方法。

// 返回包含包名的类型名字,对于未命名类型返回的是空
Name() string// Kind returns the specific kind of this type.
Kind() Kind// Implements reports whether the type implements the interface type u.
Implements(u Type) bool// AssignableTo reports whether a value of the type is assignable to type u.
AssignableTo(u Type) bool// ConvertibleTo reports whether a value of the type is convertible to type u.
ConvertibleTo(u Type) bool// Comparable reports whether values of this type are comparable.
Comparable() bool// NumMethod returns the number of exported methods in the type's method set.
NumMethod() int//通过索引值访问方法,索引值必须属于[0, NumMethod()],否则引发panic
Method(int) Method//通过方法名获取Method
MethodByName(string) (Method, bool)//返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
PkgPath() string//返回存放该类型的实例需要多大的字节空间
Size() uintptr

(2)不同基础类型的专有方法
这些方法是某种类型特有的,如果不是某种特定类型却调用了该类型的方法,则会引发panic。所以在调用特定类型的专有方法前,要清楚地知道该类型是什么,如果不确定类型,则要先调用Kind()方法确定类型后再调用类型的专有方法。

//Int*, Uint*, Float*, Complex*: Bits
//Array: Elem, Len
//Chan: ChanDir, Elem
//Func: In, NumIn, Out, NumOut, IsVariadic
//Map: Key, Elem
//Ptr: Elem
//Slice: Elem
//Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField//返回类型的元素类型,该方法只适合Array,Chan,Map,Ptr,Slice类型
Elem() Type
//返回数值型类型内存占用的位数
Bits() int
//struct类型专用的方法
//返回字段数目
NumField() int
//通过整数索引获取struct字段
Field(i int) StructField
//通过嵌入式字段获取struct字段
FiledByIndex(index []int) StructField
//通过名字查找获取struct字段
FieldByName(name string) (StructField, bool)//func类型专用的方法
//函数是否是不定参数函数
IsVariadic() bool
//输入参数个数
NumIn() int
//返回值个数
NumOut() int
//返回第i个输入参数类型
In(i int) Type
//返回第i个返回值类型
Out(i int) Type
//map类型专用的方法
//返回map key的type
Key() Type

示例代码如下:

package mainimport ("fmt""reflect"
)type Student struct {Name string "学生姓名"Age  int    `a:"111"b:"3333"`
}func main() {s := Student{}rt := reflect.TypeOf(s)fieldName, ok := rt.FieldByName("Name")//取tag数据if ok {fmt.Println(fieldName.Tag)}fieldAge, ok2 := rt.FieldByName("Age")if ok2 {fmt.Println(fieldAge.Tag.Get("a"))fmt.Println(fieldAge.Tag.Get("b"))}fmt.Println("type_Name:", rt.Name())fmt.Println("type_NumField:", rt.NumField())fmt.Println("type_PkgPath:", rt.PkgPath())fmt.Println("type_String:", rt.String())fmt.Println("type.Kind.String", rt.Kind().String())fmt.Println("type.String()=", rt.String())//获取数据结构类型的字段名称for i := 0; i < rt.NumField(); i++ {fmt.Printf("type.Field[%d].Name:=%v \n", i, rt.Field(i).Name)}sc := make([]int, 10)sc = append(sc, 1, 2, 3)sct := reflect.TypeOf(sc)//获取slice元素的Typescet := sct.Elem()fmt.Println("slice element type.Kind()=", scet.Kind())fmt.Printf("slice element type.Kind()=%d\n", scet.Kind())fmt.Println("slice element type.String()=", scet.String())fmt.Println("slice element type.Name()=", scet.Name())fmt.Println("slice type.NumMethod()=", scet.NumMethod())fmt.Println("slice type.PkgPath()=", scet.PkgPath())fmt.Println("slice type.PkgPath()=", sct.PkgPath())
}
//执行结果如下:
学生姓名
111
3333
type_Name: Student
type_NumField: 2
type_PkgPath: main
type_String: main.Student
type.Kind.String struct
type.String()= main.Student
type.Field[0].Name:=Name 
type.Field[1].Name:=Age 
slice element type.Kind()= int
slice element type.Kind()=2
slice element type.String()= int
slice element type.Name()= int
slice type.NumMethod()= 0
slice type.PkgPath()= 
slice type.PkgPath()= 

对于reflect.TypeOf(a),传进去的实参a有两种类型,一种是接口变量,另一种是具体类型变量。如果a是具体类型变量,则reflect.TypeOf()返回的是具体的类型信息;如果a是一个接口变量,则函数的返回结果又分两种情况:如果a绑定了具体类型实例,则返回的是接口的动态类型,即a绑定的具体实例类型的信息,如果a没有绑定具体类型实例,则返回的是接口自身的静态类型信息。示例代码如下:

package mainimport "reflect"type INT inttype A struct {a int
}
type B struct {b string
}
type Ita interface {String() string
}func (b B) String() string {return b.b
}
func main() {var a INT = 12var b int = 14//实参是具体类型,relfect.TypeOf返回的是其静态类型ta := reflect.TypeOf(a)tb := reflect.TypeOf(b)//INT和int是两个类型,二者不相等if ta == tb {println("ta == tb")} else {println("ta != tb") // ta != tb}println(ta.Name()) // INTprintln(tb.Name()) // int//底层基础类型println(ta.Kind().String()) // intprintln(tb.Kind().String()) // ints1 := A{1}s2 := B{"s2s2"}//实参是具体类型,reflect.TypeOf返回的是其静态类型println(reflect.TypeOf(s1).Name()) // Aprintln(reflect.TypeOf(s2).Name()) // B//Type的Kind()方法返回的是基础类型,类型A和类型B的底层基础类型都是structprintln(reflect.TypeOf(s1).Kind().String()) // structprintln(reflect.TypeOf(s2).Kind().String()) // structita := new(Ita)var itb Ita = s2//实参是未绑定具体类型的接口类型, reflect.TypeOf返回的是接口类型本身,也就是接口的静态类型println(reflect.TypeOf(ita).Elem().Name()) //Itaprintln(reflect.TypeOf(ita).Elem().Kind().String()) // interface//实参是绑定了具体变量的接口类型,reflect.TypeOf返回的是绑定的具体类型println(reflect.TypeOf(itb).Name()) // Bprintln(reflect.TypeOf(itb).Kind().String()) // struct
}

relfect.Value
reflect 包中另一个重要的类型是 Value。一个 reflect.Value 可以装载任意类型的值。函数 reflect.ValueOf 接受任意的 interface{} 类型,并返回一个装载着其动态值的 reflect.Value。和 reflect.TypeOf 类似,reflect.ValueOf 返回的结果也是具体的类型,但是 reflect.Value 也可以持有一个接口值。

通过 Type()方法和 Interface()方法可以打通 interfaceTypeValue 三者。Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法可以将 Value 还原成原来的 interface
Interface Value Type三者的关系
TypeOf()函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息; ValueOf()函数返回一个结构体变量,包含类型信息以及实际值。

reflect.Value表示实例的值信息,reflect.Value是一个struct,并提供了一系列的method给使用者。Value的基本数据结构如下:

type Value struct {// typ holds the type of the value represented by a Value.typ *rtype// Pointer-valued data or, if flagIndir is set, pointer to data. Valid when either flagIndir is set or typ.pointers() is true.ptr unsafe.Pointerflag
}

reflect.Value总共有三个字段,一个是值的类型指针typ,另一个是指向值的指针ptr,最后一个是标记字段flag。反射包中通过reflect.ValueOf()函数获取实例的值信息。reflect.ValueOf()的原型如下:

func ValueOf(i interface{}) Value

输入参数是空接口,输出是一个Value类型的变量。Value本身提供了丰富的API给用户使用。示例如下:

package main
import ("fmt""reflect"
)type User struct {Id   intName stringAge  int
}func (this User) String() {println("User: ", this.Id, this.Name, this.Age)
}
func Info(o interface{}) {// 获取Value信息v := reflect.ValueOf(o)//通过value获取Typet := v.Type()//类型名称println("Type:", t.Name())//访问接口字段名、字段类型和字段值信息println("Fields:")for i := 0; i < t.NumField(); i++ {field := t.Field(i)value := v.Field(i).Interface()//类型查询switch value := value.(type) {case int:fmt.Printf("  %6s: %v = %d\n", field.Name, field.Type, value)case string:fmt.Printf("  %6s: %v = %s\n", field.Name, field.Type, value)default:fmt.Printf("  %6s: %v = %s\n", field.Name, field.Type, value)}}
}
func main() {u := User{1, "Tom", 10}Info(u)
}
// 执行结果如下:
Type: User
Fields:Id: int = 1Name: string = TomAge: int = 10

总结:

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

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

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

  • reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是:

    • 用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值
    • 调用ValueOf函数返回一个Value类型值,该值代表运行时的数据
    • Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值
  • Go 程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息

  • 如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响。如果想要操作原变量,反射变量 Value 必须要 hold 住原变量的地址才行

  • 通过反射获取类型信息:
    通过反射获取类型信息

1.2 基础类型

Type接口有一个Kind()方法,返回的是一个整数枚举值,不同的值代表不同的类型。这里的类型是一个抽象的概念,暂且称为"基础类型"。比如所有的结构都归结为一种基础类型struct,所有的函数都为一种基础类型func。编程中,使用最多的是类型,但在反射中,当需要区分一个大品种的类型时,就会用到种类(Kind)。

Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

基础类型是根据编译器、运行时构建类型的内部数据结构不同来划分的,不同的基础类型,其构建的最终内部数据结构不一样。基础类型是抽象的,其种类优先。Go总共定义了26中基础类型,也就是种类(Kind):

// A Kind represents the specific kind of type that a Type represents. The zero Kind is not a valid kind.
type Kind uintconst (Invalid Kind = iotaBoolIntInt8Int16Int32Int64UintUint8Uint16Uint32Uint64UintptrFloat32Float64Complex64Complex128ArrayChanFuncInterfaceMapPtrSliceStringStructUnsafePointer
)

Bool、String 和 所有数字类型的基础类型;Array 和 Struct 对应的聚合类型;Chan、Func、Ptr、Slice 和 Map 对应的引用类型(channels、functions、pointers、slices 和 maps);interface 类型;还有表示空值的 Invalid 类型。

Map、Slice、Chan 使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于 Ptr。type A struct{} 定义的结构体属于 Struct 种类,*A 属于 Ptr。

底层类型和基础类型
底层类型和基础类型的区别在于:基础类型是抽象的类型划分,底层类型是针对每一个具体的类型来定义的。例如不同的struct类型在基础类型上都归为struct类型,但不同的struct底层类型是不一样的。

1.3类型汇总

  • 简单类型 复合类型 类型字面量 自定义类型
  • 命名类型 未命名类型
  • 接口类型 具体类型
  • 底层类型 基础类型
  • 动态类型 静态类型
    Go类型总结

参考资料

  1. 《Go语言核心编程》
  2. https://zhuanlan.zhihu.com/p/64884660
  3. https://www.cnblogs.com/itbsl/p/10551880.html
  4. https://books.studygolang.com/gopl-zh/ch12/ch12-02.html
http://www.lbrq.cn/news/1314829.html

相关文章:

  • 企业网站建设要注意什么/swot分析
  • 宁阳移动网站制作/谷歌seo查询
  • 营销型网站建设区别/cpa推广接单平台
  • 暖色调网页设计网站/杭州百度快照优化公司
  • 东莞网络推广案例/百度推广优化
  • seo网站推广优化公司/搜索引擎平台有哪些
  • 帮做钓鱼网站会怎样/直接下载app
  • 富阳有没有做网站的/保定网站建设方案优化
  • wap手机网站建设方案/网站seo博客
  • 常州外贸建站/西安网站定制开发
  • 宝坻网站建设制作/网站建设制作免费
  • 太仓企业网站建设公司/百度站长链接提交
  • 社区营销模式/青岛官网seo
  • 东莞网站建设多少钱/网站查询
  • 商品交换电子商务网站开发/一个产品的网络营销方案
  • 黑龙江省住房和建设厅网站/外贸建站推广公司
  • 易优cms插件/seo上排名
  • 懒人做图网站/seo是搜索引擎优化吗
  • 学网站开发首先学哪些基础/如何找外包的销售团队
  • 手机怎么防止网站跳转/星沙网站优化seo
  • 郑州做网站好/重庆seo博客
  • 火车头wordpress5.0发布模块/seo服务公司上海
  • macromedia怎么做网站/网络营销竞价推广
  • 小米商城网页设计论文/网站建设优化
  • 廊坊网站制作服务/信息流优化师培训机构
  • 如何在百度做网站推广/廊坊seo
  • 好商网的网站可以做中英文切换吗/百度经验首页
  • 一个人可以做网站吗/网络营销案例
  • 国外网站代理/网站开发需要的技术
  • 多用户购物商城源码/石家庄seo关键词
  • 深度学习图像增强方法(一)
  • redisson 设置了过期时间,会自动续期吗
  • USB读写自动化压力测试
  • 微信131~140
  • 从0到1实现Shell!Linux进程程序替换详解
  • SAP ERP与微软ERP dynamics对比,两款云ERP产品有什么区别?