Golang 接口原理

问题

小提示, 若想直接查看原理, 可从接口原理开始查看.

有这样一段GO代码:

func main() {var obj interface{}fmt.Printf("obj == nil. %b\n", obj == nil)type st struct{}var s *stobj = sfmt.Printf("s == nil. %b\n", s == nil)fmt.Printf("obj == nil. %b\n", obj == nil)
}

先盲猜一下结果.

  1. 第一次nil的判断, 结果为 true, 没什么疑问吧
  2. 第二次判断, s为空指针, 结果为true
  3. 第三次判断, objs相等, 故也为空指针, 结果为true.

如果你也是这么认为, 那么结果会令你像我一样十分惊讶:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5e4q0VX-1659519198125)(https://oss-blog.cdn.hujingnb.com/img/202208022034502.png)]

???第三次判断, obj不为nil???意不意外? 惊不惊喜? 刺不刺激? 为什么会发生这样的事情呢?

搭建 gdb 调试环境

为了知道为什么发生这种问题, 我尝试了各种方式, 断点调试, 查看汇编内容等等, 最终发现, 通过gdb工具查看十分方便.

在这之前, 先简单介绍一下gdb调试环境的使用. 不感兴趣可直接跳过

为了方便, 直接使用docker镜像了. 这里我使用的镜像为: golang:1.18 其他版本大同小异. 这里直接上结论了, 中间踩坑过程不再赘述.

# 安装 gdb 工具
apt update && apt install -y gdb
echo 'add-auto-load-safe-path /usr/local/go/src/runtime/runtime-gdb.py' > /root/.gdbinit
# 编译 go 文件. 关闭所有的优化, 防止调试时与编写的内容不一致
go build -gcflags "all=-N -l" main.go
# 进行调试
gdb ./main

是不是很简单呀.

调试与揭秘

为了方便调试, 我将无关内容去掉, 调试使用的程序如下:

package mainfunc main() {var obj interface{}type st struct{}var s *stprintln(obj == nil)obj = sprintln(obj == nil)
}

我们分别在obj赋值前后, 打印局部变量:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U0gamwOL-1659519198126)(https://oss-blog.cdn.hujingnb.com/img/202208022106041.png)]

image-20220802210623591

我们惊奇的发现, 在obj被赋值之前, obj == nilTRUE, 但是打印变量后发现, obj并不是一个空指针.

而在obj赋值之后, obj == nilFALSE. 前后的差异就在于_type字段.

在此处, 我有理由得出这样的结论:

  • golang中的interface的实现是一个结构体, 包括_type/data两个字段
  • 判断interface是否为nil时, 若两个字段均为nil, 则interfacenil, 否则不为nil.

同时, 我又好奇的查看了一下obj的类型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IrguDZyi-1659519198127)(https://oss-blog.cdn.hujingnb.com/img/202208022108216.png)]

正如上面所看到的, interface是一个特殊的类型, 其在实现上是一个叫做runtime.eface的结构体.

解惑. OK, 到这里, 就已经解答了我们最开的时候的疑惑, 在将一个空指针对象赋值给internface的时候, 会给interface结构体的字段_type赋值, 使得_type字段不为nil, 进而导致interface变量不为nil.

以上, 是我本次问题查找的原因及初步查找的过程. 我基于此对接口的实现原理进行了查阅. 后面就直接进行原理介绍, 不再穿插查找过程了, 否则着实影响观看体验.

接口原理

GO在存储接口类型的变量时, 根据接口中是否包含方法, 分别存储为不同类型的结构体.

若接口中不包含方法, 将其存储为runtime.eface. 如:

type TestInter interface {
}
var obj interface{}
var obj2 TestInter

若接口中含有方法, 则将其存储为runtime.iface. 如:

type TestInter interface {testFunc()
}
var obj2 TestInter

eface

eface定义在文件runtime2.go中. 其结构体定义如下:

type eface struct {_type *_type // 保存类型信息data  unsafe.Pointer // 保存内容
}type _type struct {size       uintptr // 类型大小ptrdata    uintptr // 没整明白是干什么用的...hash       uint32 // 类型的哈希值. 可用于快速判断类型是否相等tflag      tflag // 类型的额外信息align      uint8 // 变量的内存对齐大小fieldAlign uint8 kind       uint8 // 类型equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较此类型对象是否相等gcdata    *byte // 垃圾收集的 GC 数据str       nameOffptrToThis typeOff
}// Pointer 就是一个指针
type Pointer *ArbitraryType
type ArbitraryType int

可以看到, 在_type中基本上已经存储了一个类型的所有信息. (虽然有几个字段还没整明白, 不过对于理解整体逻辑影响不大)

_type用来对类型进行标识, 想比底层反射的实现也是根据他来的.

iface

iface区别于eface的地方, 就是iface需要额外存储接口的方法信息. 若是一个不含有方法的接口, 是可以接收所有值得. 但带有方法的接口, 则被赋值的内容必须实现了所有的方法. 其结构体定义如下:

type iface struct {tab  *itabdata unsafe.Pointer
}type itab struct {inter *interfacetype // 保存接口的信息. 用于确定变量的接口类型_type *_type // data 指向值得类型信息, 上面已经出现过了hash  uint32 // 从 _type.hash 拿过来的. 当将 interface 类型变量向下转型时, 用于快速判断. _     [4]byte// 记录接口实现的所有方法. // 若 fun[0]==0, 说明 _type 没有实现此接口. //		(没错, 是有可能没实现的. 比如转型失败)// 否则, 说明实现了此接口. 所有方法的函数指针在内存中顺序存放. // fun[0] 记录的是第一个方法的地址// 顺便提一句, 函数按照名称的字段序在内存中存放fun   [1]uintptr 
}type interfacetype struct {typ     _type // 接口类型pkgpath name // 包名mhdr    []imethod // 接口定义的方法集
}

现在知道我们在将interface类型的变量进行转型或类型断言的时候, GO是如何处理的了吧? 其实接口自己是知道自己的类型的.

另外, 在将一个结构体赋值给interface的时候, GO也在其中进行了特定的操作. 可以在runtime.iface.go文件中, 看到一批以conv开头的方法, 用来将一个变量转为数据指针unsafe.Pointer. 在此先按下不表…

总结

以上, 简单的了解了GO接口的内部实现, 发现接口在实现上和普通的结构体变量十分不同, 其内部是通过一个特定的结构体来记录信息的. 知道了接口的实现, 我们在平常开发时, 碰到接口就应该注意一下, 若interface判断不为nil, 存储的值也可能为nil.

最后, GO1.18之后增加了泛型的支持, 以前使用interface接收任意参数的场景 也可以使用泛型替代了.

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

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

相关文章

三星识别文字_比亚迪电子助力三星Galaxy Note 10系列霸气首发!

三星有子初长成气宇轩昂 秀美俊逸减之一分则嫌柔增之一分则嫌赘2019年8月7日于纽约巴克莱发布Galaxy Note 10系列用简约 重构美三星Galaxy Note 10与Galaxy Note 10分别搭载了6.3英寸和6.8英寸的超感官全视曲面屏,均采用单摄挖孔屏,开孔位于屏幕正上方。…

lisp 设计盘形齿轮铣刀_机械设计基础——周转轮系传动比的计算

点击上方蓝色字体,关注我们15(视频来源于网络,仅供学习交流,侵权请联系删除)机械计重点学习指导机械原理全书重点提要轴的结构改错机械设计作业集01机械设计作业集02机械设计作业集答案机械原理作业集机械原理作业集答案轴的强度计算院校推荐…

b+树阶怎么确定_B站公布年度弹幕,这个排名我不太服气

也忘记了是从什么时候开始,B站开始公布自己的年度弹幕了,今年的年度弹幕排名前五的分别是:爷青回、武汉加油、有内味了、双厨狂喜、禁止套娃。话说今年真的是不容易啊,过年那段时间以及上半年不会忘记那一幕幕感人深邃的瞬间&…

css打印适应纸张_从生态平衡到打印机故障分析

生态平衡(ecological equilibrium)是指在一定时间内生态系统中的生物和环境之间、生物各个种群之间,通过能量流动、物质循环和信息传递,使它们相互之间达到高度适应、协调和统一的状态。也就是说当生态系统处于平衡状态时,系统内各组成成分之…

html5调用系统声音1s响一次_20款奔驰GLC260提车改柏林之声音响,音乐诉请,为爱发声!...

奔驰GLC车型在2020上半年可谓是风生水起,尤其是2020年1-5月份的豪华品牌SUV排名中,奔驰GLC车型以58982的销售量遥遥领先,同比增长了2%,奔驰GLC5月销量高达15275辆,再次打败老对手奥迪Q5L,夺得豪华SUV销量冠…

kotlin将对象转换为map_将网站转换为Photoshop文档

WebToLayers是一款能够帮助大家将网页转换成图像格式的软件,能够Web页面转换成PNG,JPG以及PSD格式的图片。当网页转换为PSD的时候,网页的各个要素都会自动转换为相应的图层,使得大家能够对PSD格式的网页进行设计与管理。WebToLaye…

centos更换网卡后怎么更新配置_CentOS安装

服务器使用的Linux操作系统都使用了CentOS来进行安装,CentOS是一个开源的Linux发行版,具有很好的稳定性和更多的可扩展行。为了能够正常使用Docker,我们将使用CentOS7及以上版本。​下载地址:https://www.centos.org/download/ ​…

centos普通用户修改文件权限_Linux实战014:Centos创建用户并添加root授权

刚收到在腾讯云申请的云服务器8台,现在准备分配给不同项目组来使用。为了确保系统及账号的安全,root账号不能直接给到他们。因为root的权限太大,任何的误操作就可能导致系统异常或者数据丢失找不回来。而且我们这是生产环境,账号会…

mongodb 导出txt_(干货)前端实现导出excel的功能

前言 导出功能其实在开发过程中是很常见的,平时我们做导出功能的时候基本都是后台生成,我们直接只需要调一支接口后台把生成的文件放到服务器或者数据库mongodb中,如果是放到mongodb中的话,我们需要从mongodb中通过唯一生成的id去拿到文件,最后window.location.href…

1971旗舰cpu intel_CPU的历史

很多人都对电脑硬件有一点的了解,本人也算略懂一二,所以今天来为大家说说电脑的主要硬件之一––CPU(中央处理器)。那么我们知道世界上造CPU的公司主要就是Intel和AMD。其实仔细想想,CPU的主要成分是什么?是硅(Si),硅从那里来&am…

文本显示变量_【RPA课堂】UiPath中的变量、数据类型和组件

自动化出现的那一天起,就有了各种各样的工具来满足自动化的需要。无论是用于windows桌面自动化的简单工具,还是用于企业自动化大量任务的工具,它们都有自己的功能。UiPath就是这样的工具,在本文中,我们介绍一些非常基本…

bootstrap上传图片可实现查看上一张图片和下一张图片_如何实现像人民日报微信推文一样的的点亮效果?...

如何实现向人民日报微信推文一样的的点亮效果?有两种方法:方法一:就是使用代码在编辑器进行编辑emmmmmm这个方法贼麻烦,需要调至HTML模式……方法二:在现有编辑器模板下利用SVG动画进行编辑,因为点亮效果本…

设置log缓存_node多级缓存之redis缓存

在node项目开发过程中,缓存常常被用来解决高性能、高并发等问题。在我们的实际项目中,运用缓存的思路是内存缓存-->接口-->文件缓存。前面的总结中已经详细的说明了怎么实现和封装内存缓存和文件缓存。虽然二级缓存已经基本能够满足现在的所有场景…

c++实现决策树分类汽车评估数据集_R有监督机器学习-分类方法

当我们说机器学习的的时候,我们在说什么?来源于mlr3包的作者:https://mlr3book.mlr-org.com/basics.html上图解释了完整的机器学习流程,包括构建任务、准备训练数据集及测试数据集、选择学习方法(leaner)、…

lingo编程的主要方法_java并发编程 --并发问题的根源及主要解决方法

并发问题的根源在哪首先,我们要知道并发要解决的是什么问题?并发要解决的是单进程情况下硬件资源无法充分利用的问题。而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大。如果将CPU的速度比作火箭的速度,那么内存的速度就像…

Mysql中Drop删除用户的名字_mysql5.5 使用drop删除用户

在说这个问题之前我们先讨论下关于在mysql中删除用户的方法和问题:其实在以前我删除mysql中的账号的时候用delete,一直没注意其实用这个命令删除账号会有一个问题就是使用delete删除账号后,只会清除user表的,在其它表中的信息还是…

docker建多个mysql_《容器化系列二》利用Docker容器化技术安装多个mysql

前提说明安装的Linux系统版本为Centos7.x一、安装docker并测试1、安装yum相关工具包///安装yum相关工具包yum install -y yum-utils device-mapper-persistent-data lvm2//发些报错,关闭刚刚睡眠中的进程kill -9 13312//再次执行yum install -y yum-utils device-ma…

mysql 元数据获取_[MySQL] 获取元数据的步骤

[MySQL] 获取元数据的方法 MySQL提供了以下三种方法用于获取数据库对象的元数据: 1)show语句 2)从INFORMATION_SCHEMA数据库里查询相关表 3)命令行程序,如mysqlshow, mysqldump 用SHOW语句获取元数据 MySQL用show语句获取元数据是最常用的方法&#xff0…

在模糊查询中怎样事先加载页面_8种信息类型,中后台产品功能自查清单

产品经理在梳理产品需求文档时需要把每一个功能的逻辑关系、交互方式都整理全面,为了避免疏漏,与开发评审前,建议每位产品都 Check 几遍文档。本文整理了一份中后台产品功能自查清单,供大家参考,如有不全欢迎提建议~中…

mysql 8.0 手动安装教程_mysql 8.0.13手动安装教程

本文为大家分享了mysql 8.0.13手动安装教程,供大家参考,具体内容如下一、步骤解读1.下载MySQL下载地址选择 Downloads-->Community-->MySQL Community Server,然后拉到页面的最低端,点击“下载”。此时一般会提示登陆&#…