您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

golang反射实现原理

时间:2023-05-24 16:39:09  来源:今日头条  作者:干饭人小羽

 

1. 引言

反射是现代编程语言中非常重要的一个特性,尤其是在面向对象编程语言中

此前的文章中,我们看到 golang 如何实现面向对象的封装: 通过 GoLang 实现面向对象思想

如果能够熟练运用反射特性,可以在很多情况下写出通用性极强的代码,达到事半功倍的效果,那么,究竟什么是反射,在 golang 中反射又是如何实现的,本文我们就来详细解读。

2. 反射

什么是反射?为什么要使用反射?这是本文开始前必须要解决的两个问题。

2.1. 什么是反射

反射机制是现代编程语言中一个比较高级的特性。 在编译时不知道类型的情况下,通过反射机制可以获取对象的类型、值、方法甚至动态改变对象的成员,这就是反射机制。

2.2. 为什么使用反射

在很多时候,我们都希望代码具备良好的通用性。 最基本的,对于 fmt.Print 函数,他可以接受任意类型的对象作为参数,我们无法罗列出究竟有多少种入参的可能,此时,主动获取入参的类型就可以大大简化代码的编写。 更进一步,对于依赖注入与面向切面等设计模式来说,我们需要为被注入对象动态添加成员,动态调用对象的不同成员。 显然,反射的存在极大地增加了代码的灵活度与通用性。

3. golang 与反射

之前的文章中,我们讲了 golang 的接口: golang 中的接口

golang 的接口作为 golang 语言中运行时类型抽象的主要工具,它的实现与反射机制的实现有着非常密切的关联,他们都涉及 golang 中是如何管理类型的。 golang 中有两种类型:

  1. static type — 静态类型,创建变量时指定的类型,如 var str string,str 的类型 string 就是他的静态类型
  2. concrete type — 运行时类型,如一个变量实现了接口中全部方法,那么这个变量的 concrete type 就是该接口类型

所以,golang 中,反射是必须与接口类型结合使用的。

4. golang 中的接口

golang 中的接口使用下面的几个结构实现的:

type emptyInterface struct {
    typ  *rtype
    word unsafe.Pointer
}

// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
    // see ../runtime/iface.go:/Itab
    itab *struct {
        ityp *rtype // static interface type
        typ  *rtype // dynamic concrete type
        hash uint32 // copy of typ.hash
        _    [4]byte
        fun  [100000]unsafe.Pointer // method table
    }
    word unsafe.Pointer
}

type rtype struct {
    size       uintptr
    ptrdata    uintptr  // number of bytes in the type that can contain pointers
    hash       uint32   // hash of type; avoids computation in hash tables
    tflag      tflag    // extra type information flags
    align      uint8    // alignment of variable with this type
    fieldAlign uint8    // alignment of struct field with this type
    kind       uint8    // enumeration for C
    alg        *typeAlg // algorithm table
    gcdata     *byte    // garbage collection data
    str        nameOff  // string form
    ptrToThis  typeOff  // type for pointer to this type, may be zero
}

// An InterfaceType node represents an interface type.
InterfaceType struct {
    Interface  token.Pos  // position of "interface" keyword
    Methods    *FieldList // list of methods
    Incomplete bool       // true if (source) methods are missing in the Methods list
}

复制

因为 golang 中指针类型与指向区域的数据类型必须一致且不能变更,这为抽象功能的实现带来了太大的局限,于是 golang 中提供了 unsafe 包,提供了对指针的增强功能,unsafe.Pointer类似于C中的void*,任何类型的指针都可以转换为unsafe.Pointer 类型,unsafe.Pointer 类型也可以转换为任何指针类型。 从上面的代码中,我们看到,在 golang 中,不具有方法的接口类型与具有方法的接口类型是分别通过 eface 与 iface 两种类型实现的。

 

eface 与 iface 两者都同样是由两个指针来实现的,分别指向接口本身的类型描述结构与接口实现的内存空间。

4.1. 接口类型断言的实现

此前介绍接口的文章中,我们有介绍到接口的类型断言,其实现原理就是通过将断言类型的 _type 与 data 指针指向的数据空间中的 type 进行比较实现的。 因此,即使断言类型与数据类型在内存中是一模一样的,也无法通过断言实现其类型的转换:

package main

import "fmt"

type temprature int

func main() {
    var temprature interface{} = temprature(5)
    fmt.Printf("temprature is %d", temprature.(int))
}

复制

虽然在内存中,我们定义的 temprature 与 int 是相同的,但其 _type 值是不同的,因此上述代码抛出了 panic:

panic: interface conversion: interface {} is main.temprature, not int

5. 反射的实现 — reflect 包

在 golang 中,reflect 包实现了反射机制,它定义了两个重要的类型:Type 和 Value,分别用来获取接口类型变量的实际类型与值。 获取他们的方法就是 TypeOf 和 ValueOf 方法。 我们修改一下上面的示例代码:

package main

import (
    "fmt"
    "reflect"
)

type temprature int

func main() {
    var temp interface{} = temprature(5)
    fmt.Printf("temprature is %dn", temp.(temprature))
    itype := reflect.TypeOf(temp)
    ivalue := reflect.ValueOf(temp)
    fmt.Printf("%v: %v", itype, ivalue)
}

复制

打印出了:

temprature is 5 main.temprature: 5

可以看到,通过 TypeOf 与 ValueOf 方法,我们已经取到了接口变量的类型和实际的值。

6. golang 反射的实现原理

让我们来看一下 TypeOf 与 ValueOf 的实现。

6.1. TypeOf 源码

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
}

复制

TypeOf 的实现通过 unsafe.Pointer 进行指针类型的强制转换,从而通过返回的实例中获取到内存中数据的实际类型字段。

6.2. ValueOf 源码

func ValueOf(i interface{}) Value {
    if i == nil {
        return Value{}
    }
    escapes(i)

    return unpackEface(i)
}

func escapes(x interface{}) {
    if dummy.b {
        dummy.x = x
    }
}

func unpackEface(i interface{}) Value {
    e := (*emptyInterface)(unsafe.Pointer(&i))
    // NOTE: don't read e.word until we know whether it is really a pointer or not.
    t := e.typ
    if t == nil {
        return Value{}
    }
    f := flag(t.Kind())
    if ifaceIndir(t) {
        f |= flagIndir
    }
    return Value{t, e.word, f}
}

复制

ValueOf 的实现相较于 TypeOf 的实现显得略为复杂一些。 在 unpackEface 函数中,同样通过 unsafe.Pointer 将传入参数转换为了 emptyInterface 类型,从而可以获取到传入参数的类型字段与指向实际数据的指针,最终封装为 Value 类型值返回:

type flag uintptr

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}

复制

这个结构正是 golang 保存任何一个类型变量的存储结构,因此,ValueOf 的返回值可以直接作为变量值来使用。

7. 后记

那么,在实际的使用场景中,反射能够为我们带来哪些便捷的功能呢?敬请期待下一篇文章 — golang 中反射的使用。



Tags:golang   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除。
▌相关推荐
1. 引言反射是现代编程语言中非常重要的一个特性,尤其是在面向对象编程语言中此前的文章中,我们看到 golang 如何实现面向对象的封装: 通过 GoLang 实现面向对象思想如果能够...【详细内容】
2023-05-24  Tags: golang  点击:(0)  评论:(0)  加入收藏
在生产环境中,如mysql数据库服务、rabbit-mq消息队列服务、redis缓存服务等。为了安全,这些服务的通常端口都是不对外网开放的。有时候,我们需要本地访问这些服务,要如何设置呢?...【详细内容】
2023-05-16  Tags: golang  点击:(23)  评论:(0)  加入收藏
Golang中的sync包实现了两种锁:互斥锁(Mutex)和读写锁(RWMutex)。互斥锁(sync.Mutex) 使用Lock方法加锁,使用Unlock方法解锁,Golang从1.18新增了TryLock方法,用于尝试获取锁,返回成功或...【详细内容】
2023-05-15  Tags: golang  点击:(21)  评论:(0)  加入收藏
作者:xindong本文针对Golang与Java的基础语法、结构体函数、异常处理、并发编程及垃圾回收、资源消耗等各方面的差异进行对比总结,有不准确、不到位的地方还请大家不吝赐教。...【详细内容】
2023-04-07  Tags: golang  点击:(47)  评论:(0)  加入收藏
在 Golang 中,要判断两个 slice 是否相等是不能直接使用 == 运算符的(== 只能说明 两个 slice 是否指向同一个底层数组)。如果两个 slice 的底层数组相同,但长度或容量不同,则仍...【详细内容】
2023-03-31  Tags: golang  点击:(65)  评论:(0)  加入收藏
前言大家好,我是星期八,是一个每天都要在镜子前给自己梳仅剩三根头发的三年码农本次我们来安排一下如何在win平台上配置Go语言开发环境。整体来说,Go配置环境还是挺轻松的,和Py...【详细内容】
2023-03-29  Tags: golang  点击:(60)  评论:(0)  加入收藏
rune类型在看一些开源项目的源代码时,经常看到rune类型的使用,本文就详细说下rune类型,首先看下golang源码的注释:// rune is an alias for int32 and is equivalent to int32 i...【详细内容】
2023-03-18  Tags: golang  点击:(38)  评论:(0)  加入收藏
这篇文章是Go语言学习资料的精髓,可以让你终身受益,汇集Golang各种资料的精髓,以下是一些初学者如何学习golang的方法和学习资料。如果你是新手,以下就是学习Golang的过程:1.了解...【详细内容】
2023-03-16  Tags: golang  点击:(25)  评论:(0)  加入收藏
Golang和Rust都是非常适合服务器开发的现代编程语言。根据具体的需要和项目以及它们各有的特色,选择哪一个更合适。1.Golang是一种高效和并发的编程语言,它的语法简单,易于学习...【详细内容】
2023-03-14  Tags: golang  点击:(11)  评论:(0)  加入收藏
背景一谈到golang,大家的第一感觉就是高并发,高性能。但是语言本身的优势是不是,就让程序员觉得编写高性能的处理系统变得轻而易举,水到渠成呢。下面这篇文章给大家的提醒便是,...【详细内容】
2023-03-13  Tags: golang  点击:(93)  评论:(0)  加入收藏
▌简易百科推荐
1. 引言反射是现代编程语言中非常重要的一个特性,尤其是在面向对象编程语言中此前的文章中,我们看到 golang 如何实现面向对象的封装: 通过 GoLang 实现面向对象思想如果能够...【详细内容】
2023-05-24  干饭人小羽  今日头条  Tags:golang   点击:(0)  评论:(0)  加入收藏
原型设计模式是面向对象编程中的一种设计模式,它的主要目的是通过复制现有对象来创建新对象,从而避免昂贵的对象创建过程,提高程序的性能和效率。原型设计模式包括深拷贝和浅拷...【详细内容】
2023-05-22  阿琪说    Tags:Go   点击:(3)  评论:(0)  加入收藏
工厂模式是面向对象编程中的一种设计模式,它的主要目的是将对象的创建与使用分离开来,从而提高程序的可维护性和可扩展性。工厂模式包括工厂方法模式、抽象工厂模式和简单工厂...【详细内容】
2023-05-20  阿琪说    Tags:工厂模式   点击:(4)  评论:(0)  加入收藏
在遍历 Map 时,并不是固定地从 0 号 Bucket 开始遍历,每次都是从一个随机值序号的 Bucket 开始遍历,并且是从这个 Bucket 的一个随机序号的 Cell 开始遍历。Go 语言中的 map 是...【详细内容】
2023-05-19     AlwaysBeta  Tags:Go 语言   点击:(13)  评论:(0)  加入收藏
在生产环境中,如mysql数据库服务、rabbit-mq消息队列服务、redis缓存服务等。为了安全,这些服务的通常端口都是不对外网开放的。有时候,我们需要本地访问这些服务,要如何设置呢?...【详细内容】
2023-05-16  互联网解决方案服务  今日头条  Tags:golang   点击:(23)  评论:(0)  加入收藏
Go 语言中的 map 是一个非常常用的数据结构,它允许我们快速地存储和检索键值对。然而,在并发场景下使用 map 时,还是有一些问题需要注意的。本文将探讨 Go 语言中的 map 是否...【详细内容】
2023-05-15  AlwaysBeta  微信公众号  Tags:Go 语言   点击:(16)  评论:(0)  加入收藏
Golang中的sync包实现了两种锁:互斥锁(Mutex)和读写锁(RWMutex)。互斥锁(sync.Mutex) 使用Lock方法加锁,使用Unlock方法解锁,Golang从1.18新增了TryLock方法,用于尝试获取锁,返回成功或...【详细内容】
2023-05-15  路多辛  今日头条  Tags:Golang   点击:(21)  评论:(0)  加入收藏
众所周知,自媒体写作是一项需要不断创新的工作,而其中最常见的问题就是“原创难”。为了应对这一问题,人们开始寻找各种“伪原创”方法。而在这些方法中,go语言无疑是最受欢迎的...【详细内容】
2023-05-07  京京爱美食    Tags:Go语言   点击:(22)  评论:(0)  加入收藏
unsafe是Golang中的一个包,提供了一些不安全的操作,例如指针操作和类型转换,这些操作可以绕过Go语言的类型安全检查和垃圾回收机制,可以用于编写低层次的系统代码或进行一些高性...【详细内容】
2023-05-05  大厂背锅侠  今日头条  Tags:Go语言   点击:(9)  评论:(0)  加入收藏
这篇开始记录整数的格式化处理,首先学习下%c格式 可以看到,参数是Unicode编码或字符。我们知道,字符实质是一个整数,如果用Unicode编码,一般指的是UTF-16。fmt.Printf("%c\n",&#3...【详细内容】
2023-04-17  编程牛  今日头条  Tags:Go   点击:(38)  评论:(0)  加入收藏
站内最新
站内热门
站内头条