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

100m的网站 数据库网站如何加入百度网盟

100m的网站 数据库,网站如何加入百度网盟,酒店建筑设计,WordPress微说欢迎大家到我的博客浏览#xff0c;更好的阅读体验请点击 反射 | YinKais Blog 反射在大多数的应用和服务中并不常见#xff0c;但是很多框架都依赖 Go 语言的反射机制简化代码。!--more--因为 Go 语言的语法元素很少、设计简单#xff0c;所以它没有特别强的表达能…欢迎大家到我的博客浏览更好的阅读体验请点击 反射 | YinKais Blog 反射在大多数的应用和服务中并不常见但是很多框架都依赖 Go 语言的反射机制简化代码。!--more--因为 Go 语言的语法元素很少、设计简单所以它没有特别强的表达能力却可以通过 reflect 包能够弥补它在语法上reflect.Type的一些劣势。 reflect 实现了运行时的反射能力能够让程序操作不同类型的对象。反射包中有两队非常重要的函数和类型两个函数分别是 reflect.TypeOf 能获取类型信息 reflect.ValueOf 能获取数据的运行时表示 两个类型是 reflect.Type 和 reflect.Value 它们与函数是一一对应的关系 类型 reflect.Type 是反射包定义的一个接口我们可以使用 reflect.TypeOf 函数获取任意变量的类型reflect.Type 接口中定义了一些有趣的方法MethodByName 可以获取当前类型对应方法的引用、Implements 可以判断当前类型是否实现了某个接口 type Type interface {Align() int             // Align 返回类型的对齐要求。FieldAlign() int        // FieldAlign 返回字段的对齐要求。Method(int) Method      // Method 根据方法索引返回对应的 Method 值。MethodByName(string) (Method, bool) // MethodByName 根据方法名称返回对应的 Method 值如果找不到则返回 false。NumMethod() int         // NumMethod 返回接口实现的方法数量。// ... 其他方法和属性Implements(u Type) bool // Implements 返回类型是否实现了给定的接口 u。// ... 其他方法和属性 } 反射包中 reflect.Value 的类型与 reflect.Type 不同它被声明成了结构体。这个结构体没有对外暴露的字段但是提供了获取或者写入数据的方法 type Value struct {// 包含过滤的或者未导出的字段 } // Addr 返回一个 Value 结构的指针用于获取该值的地址。 func (v Value) Addr() Value // Bool 将 Value 转换为布尔值并返回结果。 func (v Value) Bool() bool // Bytes 返回 Value 的字节切片表示形式。 func (v Value) Bytes() []byte // ... 其他方法 反射包中的所有方法都是围绕着 reflect.Type 和 reflect.Value 两个类型设计的。我们通过 reflect.TypeOf 和 reflect.ValueOf 可以将一个普通的变量转换成反射包中提供的 reflect.Type 和 reflect.Value然后就可以使用反射包中的方法对它们进行复杂的操作了。 1、三大法则 运行时反射是程序在运行期间检查其自身结构的一种方式。反射带来的灵活性是一把双刃剑反射作为一种元编程方式可以减少重复代码但是过量使用反射会使我们的程序变得难以理解并且运行缓慢。下面我们将介绍 Go 语言反射的三大法则 从 interface{} 变量可以反射出反射对象 从反射对象可以获取interface{} 变量 要修改反射对象其值必须可设置 第一法则 反射的第一法则是我们能将 Go 语言的 interface{} 变量转换成反射对象。当我们执行 reflect.ValueOf(1) 时虽然看起来是获取了基本类型 int 对应的反射类型但是由于 reflect.TypeOf、reflect.ValueOf 两个方法的入参都是 interface{} 类型所以在方法执行的过程中发生了类型转换。 因为 Go 语言的函数调用都是值传递所以变量会在函数调用时进行类型转换。基本类型 int 会转换成 interface{} 类型这就是为什么第一条法则是从接口到反射类型。 上面提到的 reflect.TypeOf 和 reflect.ValueOf 函数就能完成这里的转换。如果把 Go 语言和类型和反射类型比作两个世界那么这两个函数就是连接这两个世界的桥梁。 通过下面的例子简单介绍一下它们的作用refelct.TypeOf 获取了变量author 的类型reflect.ValueOf 获取了变量的值 draven。我们知道了一个变量的类型和值就意味着我们知道了这个变量的所有信息。 package main ​ import (fmtreflect ) ​ func main() {author : dravenfmt.Println(TypeOf author:, reflect.TypeOf(author))fmt.Println(ValueOf author:, reflect.ValueOf(author)) } ​ $ go run main.go TypeOf author: string ValueOf author: draven 有了变量的类型之后我们可以通过 Method 方法获得类型实现的方法通过 Field 获取类型包含的全部字段。对于不同的类型我们也可以调用不同的方法获取相关信息 结构体获取字段的数量并通过下标和字段名后去字段 StructField 哈希表获取哈希表的 Key 类型 函数或方法获取入参和返回值类型 …… 综上所述使用 reflect.TypeOf 和 reflect.ValueOf 能够获取 Go 语言中变量对应的反射对象一旦获取了反射对象我们就能得到跟当前类型相关数据和操作并可以使用这些运行时获取的结构执行方法。 第二法则 反射的第二法则是我们可以从反射对象获取 interface{} 变量。既然能将接口类型的变量转变成反射对象那么一定需要其他方法将反射对象还原成接口类型的变量reflect 中的 reflect.Value.Interface 就能完成这项工作 不过调用 reflect.Value.Interface 方法只能获得 interface{} 类型的变量如果想要将其还原成最原始的状态还需要经过如下所示的显示类型转换 v : reflect.ValueOf(1) v.Interface().(int) 从反射对象到接口值的过程是从接口值到反射对象的镜面过程两个过程都需要经历两次转换 从接口值到反射对象 从基本类型到接口类型的类型转换 从接口类型到反射对象的转换 从反射对象到接口值 反射对象转换成接口类型 通过显式类型转换变成原始类型 当然不是所有的变量都需要类型转换这一过程。如果变量本身就是 interface{} 类型的那么它不需要类型转换因为类型转换这一过程一般都是隐式的所以我不太需要关心它只有在我们需要将反射对象转换回基本类型时才需要显式的转换操作。 第三法则 Go 语言反射的最后一条法则是与值是否可以被更改有关如果我们想要更新一个 reflect.Value那么它持有的值一定是可以被更新的假设我们有以下代码 func main() {i : 1v : reflect.ValueOf(i)v.SetInt(10)fmt.Println(i) } ​ $ go run reflect.go panic: reflect: reflect.flag.mustBeAssignable using unaddressable value ​ goroutine 1 [running]: reflect.flag.mustBeAssignableSlow(0x82, 0x1014c0)/usr/local/go/src/reflect/value.go:247 0x180 reflect.flag.mustBeAssignable(...)/usr/local/go/src/reflect/value.go:234 reflect.Value.SetInt(0x100dc0, 0x414020, 0x82, 0x1840, 0xa, 0x0)/usr/local/go/src/reflect/value.go:1606 0x40 main.main()/tmp/sandbox590309925/prog.go:11 0xe0 运行上述代码会导致程序崩溃并报出 “reflect: reflect.flag.mustBeAssignable using unaddressable value” 错误仔细思考一下就能够发现出错的原因由于 Go 语言的函数调用都是传值的所以我们得到的反射对象跟最开始的变量没有任何关系那么直接修改反射对象无法改变原始变量程序为了防止错误就会崩溃。 想要修改原变量只能使用如下的方法 func main() {i : 1v : reflect.ValueOf(i)v.Elem().SetInt(10)fmt.Println(i) } $ go run reflect.go 10 调用 reflect.ValueOf获取变量指针 调用 reflect.Value.Elem获取指针指向的变量 调用 refelct.Value.SetInt 更新变量的值 由于 Go 语言的函数调用都是值传递所以我们只能用迂回的方式改变原变量先获取指针对应的reflect.Value再通过reflect.Value.Elem方法得到可以被设置的变量我们可以通过下面的代码理解这个过程 func main() {i : 1v : i*v 10 } 如果不能直接操作 i 变量修改其持有的值我们就只能获取 i 变量所在地址并使用 *v 修改所在地址中存储的整数。 2、类型和值 Go 语言的 interface{} 类型在语言内部是通过 reflect.emptyInterface 结体表示的其中的 rtype 字段用于表示变量的类型另一个 word 字段指向内部封装的数据 type emptyInterface struct {typ  *rtypeword unsafe.Pointer } 用于获取变量类型的 reflect.TypeOf 函数将传入的变量隐式转换成 reflect.emptyInterface 类型并获取其中存储的类型信息 reflect.rtype func TypeOf(i interface{}) Type {eface : *(*emptyInterface)(unsafe.Pointer(i))return toType(eface.typ) }func toType(t *rtype) Type {if t nil {return nil}return t } reflect.rtype 是一个实现了 reflect.Type 接口的结构体该结构体实现的 reflect.rtype.String 方法可以帮助我们获取当前类型的名称 func (t *rtype) String() string {s : t.nameOff(t.str).name()if t.tflagtflagExtraStar ! 0 {return s[1:]}return s } reflect.TypeOf 的实现原理其实并不复杂它只是将一个 interface{} 变量转换成了内部的 reflect.emptyInterface 表示然后从中获取相应的类型信息。 用于获取接口值 reflect.Value 的函数 reflect.ValueOf 实现也非常简单在该函数中我们先调用了 reflect.escapes 保证当前值逃逸到堆上然后通过 reflect.unpackEface 从接口中获取 reflect.Value 结构体 func ValueOf(i interface{}) Value {if i nil {return Value{}}escapes(i)return unpackEface(i) }func unpackEface(i interface{}) Value {e : (*emptyInterface)(unsafe.Pointer(i))t : e.typif t nil {return Value{}}f : flag(t.Kind())if ifaceIndir(t) {f | flagIndir}return Value{t, e.word, f} } reflect.unpackEface 会将传入的接口转换成 reflect.emptyInterface然后将具体类型和指针包装成 reflect.Value 结构体后返回。 reflect.TypeOf 和 reflect.ValueOf 的实现都很简单。我们已经分析了这两个函数的实现现在需要了解编译器在调用函数之前做了哪些工作 package mainimport (reflect )func main() {i : 20_ reflect.TypeOf(i) }$ go build -gcflags-S -N main.go ... MOVQ $20, ..autotmp_2056(SP) // autotmp 20 LEAQ type.int(SB), AX // AX type.int(SB) MOVQ AX, ..autotmp_19280(SP) // autotmp_19280(SP) type.int(SB) LEAQ ..autotmp_2056(SP), CX // CX 20 MOVQ CX, ..autotmp_19288(SP) // autotmp_19288(SP) 20 ... 从上面这段截取的汇编语言我们可以发现在函数调用之前已经发生了类型转换上述指令将 int 类型的变量转换成了占用 16 字节 autotmp_19280(SP) ~ autotmp_19288(SP) 的接口两个 LEAQ 指令分别获取了类型的指针 type.int(SB) 以及变量 i 所在的地址。 当我们想要将一个变量转换成反射对象时Go 语言会在编译期间完成类型转换将变量的类型和值转换成了 interface{} 并等待运行期间使用 reflect 包获取接口中存储的信息。 3、更新变量 reflect.TypeOf 和 reflect.ValueOf 的实现都很简单。我们已经分析了这两个函数的实现现在需要了解编译器在调用函数之前做了哪些工作 package mainimport (reflect )func main() {i : 20_ reflect.TypeOf(i) }$ go build -gcflags-S -N main.go ... MOVQ $20, ..autotmp_2056(SP) // autotmp 20 LEAQ type.int(SB), AX // AX type.int(SB) MOVQ AX, ..autotmp_19280(SP) // autotmp_19280(SP) type.int(SB) LEAQ ..autotmp_2056(SP), CX // CX 20 MOVQ CX, ..autotmp_19288(SP) // autotmp_19288(SP) 20 ... 从上面这段截取的汇编语言我们可以发现在函数调用之前已经发生了类型转换上述指令将 int 类型的变量转换成了占用 16 字节 autotmp_19280(SP) ~ autotmp_19288(SP) 的接口两个 LEAQ 指令分别获取了类型的指针 type.int(SB) 以及变量 i 所在的地址。 当我们想要将一个变量转换成反射对象时Go 语言会在编译期间完成类型转换将变量的类型和值转换成了 interface{} 并等待运行期间使用 reflect 包获取接口中存储的信息。 4、实现协议 reflect 包还为我们提供了 reflect.rtype.Implements 方法可以用于判断某些类型是否遵循特定的接口。在 Go 语言中获取结构体的反射类型 reflect.Type 还是比较容易的但是想要获得接口类型需要通过以下方式 reflect.TypeOf((*interface)(nil)).Elem() 我们通过一个例子在介绍如何判断一个类型是否实现了某个接口。假设我们需要判断如下代码中的 CustomError 是否实现了 Go 语言标准库中的 error 接口 type CustomError struct{}func (*CustomError) Error() string {return }func main() {typeOfError : reflect.TypeOf((*error)(nil)).Elem()customErrorPtr : reflect.TypeOf(CustomError{})customError : reflect.TypeOf(CustomError{})fmt.Println(customErrorPtr.Implements(typeOfError)) // # truefmt.Println(customError.Implements(typeOfError)) // # false } 上述代码的运行结果正如我们在接口一节中介绍的 CustomError 类型并没有实现 error 接口 *CustomError 指针类型实现了 error 接口 抛开上述的执行结果不谈我们来分析一下 reflect.rtype.Implements 方法的工作原理 func (t *rtype) Implements(u Type) bool {if u nil {panic(reflect: nil type passed to Type.Implements)}if u.Kind() ! Interface {panic(reflect: non-interface type passed to Type.Implements)}return implements(u.(*rtype), t) } reflect.rtype.Implements 会检查传入的类型是不是接口如果不是接口或者是空值就会直接崩溃并中止当前程序。在参数没有问题的情况下上述方法会调用私有函数 reflect.implements 判断类型之间是否有实现关系 func implements(T, V *rtype) bool {t : (*interfaceType)(unsafe.Pointer(T))if len(t.methods) 0 {return true}...v : V.uncommon()i : 0vmethods : v.methods()for j : 0; j int(v.mcount); j {tm : t.methods[i]tmName : t.nameOff(tm.name)vm : vmethods[j]vmName : V.nameOff(vm.name)if vmName.name() tmName.name() V.typeOff(vm.mtyp) t.typeOff(tm.typ) {if i; i len(t.methods) {return true}}}return false } 如果接口中不包含任何方法就意味着这是一个空的接口任意类型都自动实现该接口这时会直接返回 true。 在其他情况下由于方法都是按照字母序存储的reflect.implements 会维护两个用于遍历接口和类型方法的索引 i 和 j 判断类型是否实现了接口因为最多只会进行 n 次比较类型的方法数量所以整个过程的时间复杂度是 O(n)。 5、方法调用 作为一门静态语言如果我们想要通过 reflect 包利用反射在运行期间执行方法不是一件容易的事情下面的十几行代码就使用反射来执行 Add(0, 1) 函数 func Add(a, b int) int { return a b }func main() {v : reflect.ValueOf(Add)if v.Kind() ! reflect.Func {return}t : v.Type()argv : make([]reflect.Value, t.NumIn())for i : range argv {if t.In(i).Kind() ! reflect.Int {return}argv[i] reflect.ValueOf(i)}result : v.Call(argv)if len(result) ! 1 || result[0].Kind() ! reflect.Int {return}fmt.Println(result[0].Int()) // # 1 } 通过调用 reflect.ValueOf 获取函数 Add 对应的反射对象 通过调用 reflect.rtype.NumIn 获取函数的入参个数 多次调用 reflect.ValueOf 函数逐一设置argv数组中的各个参数 调用反射对象 Add 的 relfect.Value.Call 方法并传入参数列表 获取返回值数组、验证数组的长度以及类型并打印其中的数据 使用反射来调用方法非常复杂原本只需要一行代码就能完成的工作现在需要十几行代码才能完成但这也是在静态语言中使用动态特性需要付出的成本。 func (v Value) Call(in []Value) []Value {v.mustBe(Func)v.mustBeExported()return v.call(Call, in) } reflect.Value.Call 是运行时调用方法的入口它通过两个 MustBe 开头的方法确定了当前反射对象的类型是函数以及可见性随后调用 reflect.Value.call 完成方法调用这个私有方法的执行过程会分成以下的几个部分 检查输入参数以及类型的合法性 将传入的 reflect.Value 参数数组设置到栈上 通过函数指针和输入参数调用函数 从栈上获取函数的返回值 我们将按照上面的顺序分析使用 reflect 进行函数调用的几个过程。 参数检查 参数检查是通过反射调用方法的第一步在参数检查期间我们会从反射对象中取出当前的函数指针 unsafe.Pointer如果该函数指针是方法那么我们会通过 reflect.methodReceiver 获取方法的接收者和函数指针。 func (v Value) call(op string, in []Value) []Value {t : (*funcType)(unsafe.Pointer(v.typ))...if v.flagflagMethod ! 0 {rcvr vrcvrtype, t, fn methodReceiver(op, v, int(v.flag)flagMethodShift)} else {...}n : t.NumIn()if len(in) n {panic(reflect: Call with too few input arguments)}if len(in) n {panic(reflect: Call with too many input arguments)}for i : 0; i n; i {if xt, targ : in[i].Type(), t.In(i); !xt.AssignableTo(targ) {panic(reflect: op using xt.String() as type targ.String())}} 上述方法还会检查传入参数的个数以及参数的类型与函数签名中的类型是否可以匹配任何参数的不匹配都会导致整个程序的崩溃中止。 准备参数 当我们已经对当前方法的参数完成验证后就会进入函数调用的下一个阶段为函数调用准备参数在前面函数调用一节中我们已经介绍过 Go 语言的函数调用惯例函数或者方法在调用时所有的参数都会被依次放到栈上。 nout : t.NumOut()frametype, _, retOffset, _, framePool : funcLayout(t, rcvrtype)var args unsafe.Pointerif nout 0 {args framePool.Get().(unsafe.Pointer)} else {args unsafe_New(frametype)}off : uintptr(0)if rcvrtype ! nil {storeRcvr(rcvr, args)off ptrSize}for i, v : range in {targ : t.In(i).(*rtype)a : uintptr(targ.align)off (off a - 1) ^ (a - 1)n : targ.size...addr : add(args, off, n 0)v v.assignTo(reflect.Value.Call, targ, addr)*(*unsafe.Pointer)(addr) v.ptroff n} 通过 reflect.funcLayout 计算当前函数需要的参数和返回值的栈布局也就是每一个参数和返回值所占的空间大小 如果当前函数有返回值需要为当前函数的参数和返回值分配一片内存空间 args 如果当前函数是方法需要向将方法的接收接收者者拷贝到 args 内存中 将所有函数的参数按照顺序依次拷贝到对应 args 内存中 使用 reflect.funcLayout 返回的参数计算参数在内存中的位置 将参数拷贝到内存空间中 准备参数是计算各个参数和返回值占用的内存空间并将所有的参数都拷贝内存空间对应位置的过程该过程会考虑函数和方法、返回值数量以及参数类型带来的差异。 调用函数 准备好调用函数需要的全部参数后就会通过下面的代码执行函数指针了。我们会向该函数传入栈类型、函数指针、参数和返回值的内存空间、栈的大小以及返回值的偏移量 call(frametype, fn, args, uint32(frametype.size), uint32(retOffset)) 上述函数实际上并不存在它会在编译期间链接到 reflect.reflectcall 这个用汇编实现的函数上我们在这里不会分析该函数的具体实现感兴趣的读者可以自行了解其实现原理。 处理返回值 当函数调用结束之后就会开始处理函数的返回值 如果函数没有任何返回值会直接清空 args 中的全部内容来释放内存空间 如果当前函数有返回值 将 args 中与输入参数有关的内存空间清空 创建一个 nout 长度的切片用于保存由反射对象构成的返回值数组 从函数对象中获取返回值的类型和内存大小将 args 内存中的数据转换成 reflect.Value 类型并存储到切片中 var ret []Valueif nout 0 {typedmemclr(frametype, args)framePool.Put(args)} else {typedmemclrpartial(frametype, args, 0, retOffset)ret make([]Value, nout)off retOffsetfor i : 0; i nout; i {tv : t.Out(i)a : uintptr(tv.Align())off (off a - 1) ^ (a - 1)if tv.Size() ! 0 {fl : flagIndir | flag(tv.Kind())ret[i] Value{tv.common(), add(args, off, tv.Size() ! 0), fl}} else {ret[i] Zero(tv)}off tv.Size()}}return ret } 由 reflect.Value 构成的 ret 数组会被返回到调用方到这里为止使用反射实现函数调用的过程就结束了。 5、小结 Go 语言的 reflect 包为我们提供了多种能力包括如何使用反射来动态修改变量、判断类型是否实现了某些接口以及动态调用方法等功能通过分析反射包中方法的原理能帮助我们理解之前看起来比较怪异、令人困惑的现象。
http://www.tj-hxxt.cn/news/231010.html

相关文章:

  • 移动互联和网站开发160 作者 网站建设
  • 双体系建设网站手机论坛
  • 网站jquery上传源代码苏州建设工程信息网
  • 网站建设电脑维修数据恢复酒店网站建设因素
  • powershell做网站建设银行的网站用户名是什么
  • 网站建设方案交换认苏州久远网络湖州做网站优化
  • 福州微信网站制作设计公司网站页面设计
  • 大兴网站制作找客户的软件有哪些
  • 宁波网站建设制作电话号码网络广告案例以及分析
  • 汕尾网站建设青岛网站建设搭建
  • 论坛推广网站网络推广宣传方式
  • wordpress建站详解有哪些好的响应式网站有哪些
  • 网站做强制访问控制wordpress主题生成
  • 云南建设监理协会网站wordpress结合tornado
  • 网站建设教程免费湖南岚鸿天津搜索引擎推广系统
  • 网站开发word怎么自己找外贸订单
  • 有没有做美食的小视频网站公司网站建设方案建议
  • 营销型网站可以吗seo优化内页排名
  • html搭建网站wordpress全站cdn
  • 网站推广营销方法wordpress 模板 外贸
  • 龙岩网页优化网站使用体验
  • 高级网站建设做网站和app哪个简单
  • 四川住房城乡和城乡建设厅网站苏州竞价托管
  • 大连seo网站石家庄建设一个网站多少钱
  • 免费网站的资源可以发公众号吗广告代理商是什么
  • 广东网站开发软件现在做推广有什么好的方法
  • 做网站的什么公司最好分类信息网址
  • 购物网站制作教程北京公司网站制作要多少钱
  • 罗湖做网站运营乐云seo网址大全免费下载
  • 外贸网站布局网站群建设平台