golang 结构体断言_Golang中的reflect原理

60be8cb92a6cdaf3c6f6ea629bd89367.png

反射(reflect)是在计算机程序运行时,访问,检查,修改它自身的一种能力,是元编程的一种形式。在Java等语言中都很好地支持了反射。Golang也实现了反射,主要核心位于reflect包,官方文档为:

https://golang.org/pkg/reflect/​golang.org

本文将主要介绍Golang中的反射原理和支持的反射操作。

1. reflect原理:结构体与关系

Golang是强类型语言,每一个对象都有具体的静态类型。为什么说是静态类型呢?举个例子,如下代码:

type MyInt intvar a int
var b MyInt

a和b在Go中会被认为是不同的类型,即不会被隐式转换。另外,在Golang中的对象其实是同时记录了两个信息:变量的真实值,与该变量的类型描述。具体地,interface {}在内部是通过emptyInterface结构体表示的,结构体的定义如下:

type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

其中, typ为类型信息,word为指针。

另外interface{}是一个没有函数定义的接口定义,Golang中的继承实现是通过比较函数判断的。也就是说,所有的结构体都实现了默认接口interface{},这也是为什么所有值都能够隐式赋值给interface{}的原因。

以上的结构体便是Golang中反射的核心,也就是通过操作该结构来进行反射运算,包括获取对象的类型信息(rtype)和具体值(word指针),rtype的定义如下:

// 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
}

定义了类型需要的数据。

1.1 reflect中的结构体

在Golang的反射中,另外两个核心结构体是Type和Value。

Type是描述类型信息的接口,包括:结构体的对齐方式、方法、字段、包路径、与其他结构体的关系等,具体定义可以参考源码:

https://golang.org/src/reflect/type.go?s=1310:7552#L27​golang.org

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.Pointer// flag holds metadata about the value.// The lowest bits are flag bits://	- flagStickyRO: obtained via unexported not embedded field, so read-only//	- flagEmbedRO: obtained via unexported embedded field, so read-only//	- flagIndir: val holds a pointer to the data//	- flagAddr: v.CanAddr is true (implies flagIndir)//	- flagMethod: v is a method value.// The next five bits give the Kind of the value.// This repeats typ.Kind() except for method values.// The remaining 23+ bits give a method number for method values.// If flag.kind() != Func, code can assume that flagMethod is unset.// If ifaceIndir(typ), code can assume that flagIndir is set.flag// A method value represents a curried method invocation// like r.Read for some receiver r. The typ+val+flag bits describe// the receiver r, but the flag's Kind bits say Func (methods are// functions), and the top bits of the flag give the method number// in r's type's method table.
}

1.2 reflect对象的关系

reflect中的对象关系如下图所示,Type和Value称为反射对象,interface{}和Special Type是应用程序中的对象,其中,Special Type指应用程序中的具体类型。具体关系如下:

d61109ff511c3ef33dd0798aa2c35b32.png
反射对象关系图

1) 从接口值到反射对象

interface{} -> Type: 通过reflect.TypeOf(interface{})获得interface的类型信息对象;

interface{} -> Value:通过reflect.ValueOf(interface{})获得interface的Value反射类型对象;

2) 从反射对象到接口值

Value->interface{}:通过Value.Interface()方法可以获得值对象Value对应的接口;注意,这里不能够直接获得具体类型,如果要获得具体类型,还需要显式地进行转换。例如,

var f float64 = 3.1415
v := reflect.ValueOf(f)   // f 隐式地被转成了interface{}
y := v.Interface().(float64) // y的类型是float64

其中1)和2)两条关系也是Golang反射中三条大规则中的前两条。另外第三条是:

3) 想要修改一个反射对象,那么该值必须是可以被设置的

这个可以一个例子进行说明。如下:

var f float64 = 3.1415
v := reflect.ValueOf(f)   // f 隐式地被转成了interface{}
v.SetFloat(2.873)      // Error: 发生Panic

以上在最后一行代码将会抛出异常:

panic: reflect.Value.SetFloat using unaddressable value

也就是说,Value v指向的不是一个可寻址的值,简单地说就是不是一个地址块。但是如果改成如下代码:

var f float64 = 3.1415
v := reflect.ValueOf(&f)   // 传了f的指针,&f 隐式地被转成了interface{}
v.SetFloat(2.873)      // 成功修改

综上,也就是说当Value中管理的值是一个可被寻址的值那么改置便是一个可被修改的Value。

或者换一个方式去理解,在Golang中方法调用是值传递,然后,假如我们想要该一个方法中修改某一个对象的值,那么我们应该将指向该值的指针传入,而不是直接将值传入。

4) Type/Value转换

Value->Type:可以通过Value.Type()方法获得;而Type->Value是指创建一个Type的实例对象,则可以通过reflect.New(typ)等方法创建。

2. reflect中的结构体与方法

这里分五个维度进行介绍reflect中的结构和方法,便于理解反射的使用方法。这些操作最终都会落到前面定义的结构体emptyInterface,除在外层封装中变能够确定的方法外。

2.1 结构体

reflect中的结构体主要包括:Type,Value,ChanDir,Kind,MapIter,Method,SelectCase,SelectDir,SliceHeader,StringHeader,StructField,StructTag,ValueError等。其中,Type和Value之前已经介绍过了。

  • ChanDir:管道的方向,有三个值:RecvDir/SendDir/BothDir,分别为接受,发送,双向;
  • Kind:Type中的类型信息,包括:Invalid, 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,
  • MapIter:Map的迭代器,包括三个方法:Key、Value、Next
  • Method:描述方法的信息,包括:方法名,包路径,类型,函数,所处的下表;
  • SelectCase:描述select 操作的信息,case的方向SelectDir,使用的Channel,发送的值Send;
  • SelectDir:描述SelectCase中的方向,有三个值:SelectSend/SelectRecv/SelectDefault
  • SliceHeader:描述切片Slice的信息,包括指针,长度,容量;
  • StringHeader:描述字符串string的信息,包括指针,长度;
  • StructField:描述结构体中的域field中的信息,包括:域名,包路径,类型,标签Tag,在结构体中的偏移量offset,Type.FieldByIndex中的下标index,是否是匿名;
  • StructTag:描述标签信息,有两个方法:Get、Lookup
  • ValueError:在调用一个Value不支持的方法时会报错,并记录到ValueError中。

2.2 reflect静态方法

reflect的静态方法主要用于反射对象的的操作,包括如下:

  • reflect.Copy(dst, src Value) int:将src对象(Slice或Array)复制给dst对象,返回复制的个数;
  • func DeepEqual(x, y interface{}) bool:比较两个对象是否是“深度相等”。具体如何比较可以参考:https://golang.org/pkg/reflect/#DeepEqual
  • func Swapper(slice interface{}) func(i, j int):生成一个Swapper交换方法,必须为slice;

2.3 域Type相关的方法

该类方法主要定义在https://golang.org/src/reflect/type.go?s=78900:78939#L2811中,包括,返回值都是Type:

  • reflect.ArrayOf:创建一个指定Type和个数的数组;
  • reflect.ChanOf:创建一个类型和方向的管道类型;
  • reflect.FuncOf:创建一个指定输入/输出/是否可变(variadic)的函数定义;
  • reflect.MapOf:创建一个指定key/value类型的Map类型;
  • reflect.PtrTo:创建一个类型的指针;
  • reflect.SliceOf:创建一个类型的Slice类型;
  • reflect.StructOf:创建一个指定StructField 列表的结构体定义类型;
  • reflect.TypeOf:获得interface的类型;

2.4 值Value相关静态方法

该类方法主要定义在https://golang.org/src/reflect/value.go?s=60334:60372#L2014中,包括:

  • reflect.Append:将值append到一个Slice中,并且返回结果;
  • reflect.AppendSlice:将一个slice append到slice中;
  • reflect.Indirect:返回该Value的指向对象,如果是nil,返回零值,如果非指针,返回该值;
  • reflect.MakeChan:创建一个执行类型和大小的channel;
  • reflect.MakeFunc:在指定类型上创建一个指定定义的函数;
  • reflect.MakeMap:创建一个指定类型的map;
  • reflect.MakeMapWithSize:同上,指定大小;
  • reflect.MakeSlice:创建Slice;
  • reflect.New:创建一个指定类型的实例对象;
  • reflect.NewAt:指定了指针类型?
  • reflect.Select:创建一个select操作,需指定SelectCase
  • reflect.ValueOf:获得接口interface{}的Value;
  • reflect.Zero:创建一个指定类型的零值;

2.5 Value的实例方法

这里将不一一介绍Value的实例方法,大致可以分成三类:

  • 判断性方法:判断是否具有某些特性/能力;
  • 访问性方法:访问值的一些属性;
  • 修改性方法:修改Value中特性/值的方法;

这里介绍一个函数Elem(),该函数返回的是interface{}中包含的值或指针指向的值,如果value的类型不是reflect.Ptr,那么将返回零值。

具体如下:

func (v Value) Addr() Value
func (v Value) Bool() bool
func (v Value) Bytes() []byte
func (v Value) Call(in []Value) []Value// 最后会进入汇编代码进行方法调用
func (v Value) CallSlice(in []Value) []Value
func (v Value) CanAddr() bool
func (v Value) CanInterface() bool
func (v Value) CanSet() bool
func (v Value) Cap() int
func (v Value) Close()
func (v Value) Complex() complex128
func (v Value) Convert(t Type) Value
func (v Value) Elem() Value
func (v Value) Field(i int) Value
func (v Value) FieldByIndex(index []int) Value
func (v Value) FieldByName(name string) Value
func (v Value) FieldByNameFunc(match func(string) bool) Value
func (v Value) Float() float64
func (v Value) Index(i int) Value
func (v Value) Int() int64
func (v Value) Interface() (i interface{})
func (v Value) InterfaceData() [2]uintptr
func (v Value) IsNil() bool
func (v Value) IsValid() bool
func (v Value) IsZero() bool
func (v Value) Kind() Kind
func (v Value) Len() int
func (v Value) MapIndex(key Value) Value
func (v Value) MapKeys() []Value
func (v Value) MapRange() *MapIter
func (v Value) Method(i int) Value
func (v Value) MethodByName(name string) Value
func (v Value) NumField() int
func (v Value) NumMethod() int
func (v Value) OverflowComplex(x complex128) bool
func (v Value) OverflowFloat(x float64) bool
func (v Value) OverflowInt(x int64) bool
func (v Value) OverflowUint(x uint64) bool
func (v Value) Pointer() uintptr
func (v Value) Recv() (x Value, ok bool)
func (v Value) Send(x Value)
func (v Value) Set(x Value)
func (v Value) SetBool(x bool)
func (v Value) SetBytes(x []byte)
func (v Value) SetCap(n int)
func (v Value) SetComplex(x complex128)
func (v Value) SetFloat(x float64)
func (v Value) SetInt(x int64)
func (v Value) SetLen(n int)
func (v Value) SetMapIndex(key, elem Value)
func (v Value) SetPointer(x unsafe.Pointer)
func (v Value) SetString(x string)
func (v Value) SetUint(x uint64)
func (v Value) Slice(i, j int) Value
func (v Value) Slice3(i, j, k int) Value
func (v Value) String() string
func (v Value) TryRecv() (x Value, ok bool)
func (v Value) TrySend(x Value) bool
func (v Value) Type() Type
func (v Value) Uint() uint64
func (v Value) UnsafeAddr() uintptr

3. 总结

最后简单总结一下,本文首先介绍了Golang中反射的原理,包括其中的核心结构体和关系;然后介绍了Golang中reflect包下包含的结构体,静态函数,Type静态函数,Value静态函数和Value的实例函数。

参考

https://golang.org/pkg/reflect/https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/

Wenguang Liu:Golang中Routine闭包中的一个坑​zhuanlan.zhihu.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/393744.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「hadoop」cdh5.12离线安装(未完成)

cdh 5.12.1 安装 目前还未成功,仅供个人记录。【下载必备工具】当前系统为win7_x641、vmware虚拟机12.52、ubuntu16.04服务器版本3、windows下的ssh工具,采用git bash4、jdk安装包jdk-8u144-linux-x64.tar.gz5、mysql java驱动包 mysql-connector-java-5…

转载 JDK + Android-SDK + Python + MonkeyRunner 的安装

转载来自: 小海豚的博客 http://blog.sina.com.cn/u/1295334083 我只是搬运工。。。 JDK Android-SDK Python MonkeyRunner 的安装 1. Android-SDK介绍2. 安装 JDK, Android-SDK(包含MonkeyRunner) , Python 3. 设置环境变量4. 验证是否安装成功1. …

跟随器反馈回路电阻_如何将短反馈回路设置为单独编码器

跟随器反馈回路电阻I’ve spent the last couple years as a solo freelance developer. Comparing this experience to previously working in companies, I’ve noticed that those of us who work alone can have fewer iterative opportunities for improvement than devel…

leetcode991. 坏了的计算器(贪心)

在显示着数字的坏计算器上,我们可以执行以下两种操作: 双倍(Double):将显示屏上的数字乘 2; 递减(Decrement):将显示屏上的数字减 1 。 最初,计算器显示数字…

模块怎么用_Android 组件化/模块化 的理解!

作者:前行的乌龟到现在组件化真的不是什么新鲜东西了,大公司都用的滚瓜烂熟,龙飞凤舞了,也就是现在部分中型项目和小项目在组件化的路上努力。所以同志们,组件化没玩过的,不熟悉的赶紧搞起来,说…

操作系统基础

操作系统基础一个完整的操作系统包括 ( kernel application)内核 应用程序而我们要学习操作系统:Linux操作系统我们平时所用的WINDOWS和MS-DOS都是微软出的,而Linux不是微软出的,Linux的最大好处是非商业软件&#x…

leetcode1247. 交换字符使得字符串相同(贪心)

有两个长度相同的字符串 s1 和 s2,且它们其中 只含有 字符 “x” 和 “y”,你需要通过「交换字符」的方式使这两个字符串相同。 每次「交换字符」的时候,你都可以在两个字符串中各选一个字符进行交换。 交换只能发生在两个不同的字符串之间…

interop_如何在Blazor中实现JavaScript Interop

interop介绍 (Introduction) In this article, we will learn about JavaScript Interop in Blazor. We will understand what JavaScript Interop is and how we can implement it in Blazor with the help of a sample application.在本文中,我们将学习Blazor中Ja…

Centos 7和 Centos 6开放查看端口 防火墙关闭打开

Centos 7 firewall 命令: 查看已经开放的端口: firewall-cmd --list-ports 开启端口 firewall-cmd --zonepublic --add-port80/tcp --permanent 命令含义: –zone #作用域 –add-port80/tcp #添加端口,格式为:端口/通讯…

和get redis_SpringBoot整合Redis,你get了吗?

Our-task介绍本篇博客是我github上our-task:一个完整的清单管理系统的配套教程文档,这是SpringBootVue开发的前后端分离清单管理工具,仿滴答清单。目前已部署在阿里云ECS上,可进行在线预览,随意使用(附详细…

linux课程设计qq,仿QQ聊天系统课程设计.doc

目录绪论1一.需求分析11.1软件功能需求分析21.2 安全需求分析2二.总体设计32.1 软件结构图32.2 功能描述32.2.1注册功能概要42.2.2登录功能概要42.2.3聊天功能概要52.3 安全设计6三.数据库设计63.1概念结构设计63.2逻辑结构设计73.3物理结构设…

ocp linux 基础要点

基本命令: 创建/修改/删除用户 useradd/usermod/userdel 创建/修改/删除用户组 groupadd/groupmod/groupdel 修改所属用户/所属用户组 chown/chgrp 修改权限 chmod 创建文件夹 mkdir 创建文件 touch 切换目录 …

leetcode1386. 安排电影院座位(贪心)

如上图所示,电影院的观影厅中有 n 行座位,行编号从 1 到 n ,且每一行内总共有 10 个座位,列编号从 1 到 10 。 给你数组 reservedSeats ,包含所有已经被预约了的座位。比如说,researvedSeats[i][3,8] &…

首席技术执行官_如何在几分钟内找到任何首席执行官的电子邮件地址

首席技术执行官by Theo Strauss由西奥斯特劳斯(Theo Strauss) 如何在几分钟内找到任何首席执行官的电子邮件地址 (How to find any CEO’s email address in minutes) 银河电子邮件指南:第一部分 (The Emailer’s Guide To The Galaxy: Part I) I’m 17, so my net…

Linux 查看磁盘或文件夹及文件大小

当磁盘大小超过标准时会有报警提示,这时如果掌握df和du命令是非常明智的选择。 df可以查看一级文件夹大小、使用比例、档案系统及其挂入点,但对文件却无能为力。 du可以查看文件及文件夹的大小。 两者配合使用,非常有效。比如用df查看哪个…

Python列表基础

列表:创建列表:list[] 注意:列表里面类型可以是不同的类型 取值:list[2]   替换:注意不要越界(下表超出了可表示范围) 操作: 合并列表:   list3list2list1 列表的重复:   (list8*3)   判断元素是否…

树莓派 触摸屏_如何用树莓派搭建一个颗粒物(PM2.5)传感器

用树莓派、一个廉价的传感器和一个便宜的屏幕监测空气质量。-- Stephan Tetzel(作者)大约一年前,我写了一篇关于如何使用树莓派和廉价传感器测量 空气质量 的文章。我们这几年已在学校里和私下使用了这个项目。然而它有一个缺点:由于它基于无线/有线网&a…

shell 25个常用命令

1.列出所有目录使用量,并按大小排序。 ls|xargs du -h|sort -rn #不递归下级目录使用du -sh2.查看文件排除以#开关和空白行,适合查看配置文件。 egrep -v "^#|^$" filenamesed /#.*$/d; /^ *$/d3.删除空格和空行。 sed /^$/d filename #删除空…

tensorflow入门_TensorFlow法律和统计入门

tensorflow入门by Daniel Deutsch由Daniel Deutsch TensorFlow法律和统计入门 (Get started with TensorFlow on law and statistics) What this is about 这是关于什么的 What we will use 我们将使用什么 Get started 开始吧 Shell commands for installing everything you …

centos7 nginx+php5.6+mysql安装与配置

安装与配置 php 56的安装 php的配置写在 php.ini,可在phpinfo()中查看 //查找已安装 yum list installed | grep php // php卸载 yum -y remove php56* yum remove httpd* php* 可用的资源:centos 安装php56nginx nginx php-fpm nginx安装 sudo rpm -Uv…