首页下载APPIT技术

正在上传…重新上传取消​吴德宝AllenWu关注赞赏支持

Golang interface 全面介绍

正在上传…重新上传取消​

吴德宝AllenWu关注

0.6552018.01.31 23:04:49字数 2,666阅读 20,660

[TOC]

Golang interface 全面介绍

interface 介绍

如果说goroutine和channel是Go并发的两大基石,那么接口是Go语言编程中数据类型的关键。在Go语言的实际编程中,几乎所有的数据结构都围绕接口展开,接口是Go语言中所有数据结构的核心。

Go不是一种典型的OO语言,它在语法上不支持类和继承的概念。

没有继承是否就无法拥有多态行为了呢?答案是否定的,Go语言引入了一种新类型—Interface,它在效果上实现了类似于C++的“多态”概念,虽然与C++的多态在语法上并非完全对等,但至少在最终实现的效果上,它有多态的影子。

虽然Go语言没有类的概念,但它支持的数据类型可以定义对应的method(s)。本质上说,所谓的method(s)其实就是函数,只不过与普通函数相比,这类函数是作用在某个数据类型上的,所以在函数签名中,会有个receiver(接收器)来表明当前定义的函数会作用在该receiver上。

Go语言支持的除Interface类型外的任何其它数据类型都可以定义其method(而并非只有struct才支持method),只不过实际项目中,method(s)多定义在struct上而已。
从这一点来看,我们可以把Go中的struct看作是不支持继承行为的轻量级的“类”。

从语法上看,Interface定义了一个或一组method(s),这些method(s)只有函数签名,没有具体的实现代码(有没有联想起C++中的虚函数?)。若某个数据类型实现了Interface中定义的那些被称为"methods"的函数,则称这些数据类型实现(implement)了interface。这是我们常用的OO方式,如下是一个简单的示例

   type MyInterface interface{Print()}func TestFunc(x MyInterface) {}type MyStruct struct {}func (me MyStruct) Print() {}func main() {var me MyStructTestFunc(me)}

Why Interface

为什么要用接口呢?在Gopher China 上的分享中,有大神给出了下面的理由:

writing generic algorithm (泛型编程)

hiding implementation detail (隐藏具体实现)

providing interception points

下面大体再介绍下这三个理由

writing generic algorithm (泛型编程)

严格来说,在 Golang 中并不支持泛型编程。在 C++ 等高级语言中使用泛型编程非常的简单,所以泛型编程一直是 Golang 诟病最多的地方。但是使用 interface 我们可以实现泛型编程,如下是一个参考示例

    package sort// A type, typically a collection, that satisfies sort.Interface can be// sorted by the routines in this package.  The methods require that the// elements of the collection be enumerated by an integer index.type Interface interface {// Len is the number of elements in the collection.Len() int// Less reports whether the element with// index i should sort before the element with index j.Less(i, j int) bool// Swap swaps the elements with indexes i and j.Swap(i, j int)}...// Sort sorts data.// It makes one call to data.Len to determine n, and O(n*log(n)) calls to// data.Less and data.Swap. The sort is not guaranteed to be stable.func Sort(data Interface) {// Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.n := data.Len()maxDepth := 0for i := n; i > 0; i >>= 1 {maxDepth++}maxDepth *= 2quickSort(data, 0, n, maxDepth)}

Sort 函数的形参是一个 interface,包含了三个方法:Len(),Less(i,j int),Swap(i, j int)。使用的时候不管数组的元素类型是什么类型(int, float, string…),只要我们实现了这三个方法就可以使用 Sort 函数,这样就实现了“泛型编程”。

这种方式,我在闪聊项目里面也有实际应用过,具体案例就是对消息排序。

下面给一个具体示例,代码能够说明一切,一看就懂:

   type Person struct {Name stringAge  int}func (p Person) String() string {return fmt.Sprintf("%s: %d", p.Name, p.Age)}// ByAge implements sort.Interface for []Person based on// the Age field.type ByAge []Person //自定义func (a ByAge) Len() int           { return len(a) }func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }func main() {people := []Person{{"Bob", 31},{"John", 42},{"Michael", 17},{"Jenny", 26},}fmt.Println(people)sort.Sort(ByAge(people))fmt.Println(people)}

hiding implementation detail (隐藏具体实现)

隐藏具体实现,这个很好理解。比如我设计一个函数给你返回一个 interface,那么你只能通过 interface 里面的方法来做一些操作,但是内部的具体实现是完全不知道的。

例如我们常用的context包,就是这样的,context 最先由 google 提供,现在已经纳入了标准库,而且在原有 context 的基础上增加了:cancelCtx,timerCtx,valueCtx。

刚好前面我们有专门说过context,现在再来回顾一下

    func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := newCancelCtx(parent)propagateCancel(parent, &c)return &c, func() { c.cancel(true, Canceled) }}

表明上 WithCancel 函数返回的还是一个 Context interface,但是这个 interface 的具体实现是 cancelCtx struct。

   // newCancelCtx returns an initialized cancelCtx.func newCancelCtx(parent Context) cancelCtx {return cancelCtx{Context: parent,done:    make(chan struct{}),}}// A cancelCtx can be canceled. When canceled, it also cancels any children// that implement canceler.type cancelCtx struct {Context     //注意一下这个地方done chan struct{} // closed by the first cancel call.mu       sync.Mutexchildren map[canceler]struct{} // set to nil by the first cancel callerr      error                 // set to non-nil by the first cancel call}func (c *cancelCtx) Done() <-chan struct{} {return c.done}func (c *cancelCtx) Err() error {c.mu.Lock()defer c.mu.Unlock()return c.err}func (c *cancelCtx) String() string {return fmt.Sprintf("%v.WithCancel", c.Context)}

尽管内部实现上下面三个函数返回的具体 struct (都实现了 Context interface)不同,但是对于使用者来说是完全无感知的。

    func WithCancel(parent Context) (ctx Context, cancel CancelFunc)    //返回 cancelCtxfunc WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) //返回 timerCtxfunc WithValue(parent Context, key, val interface{}) Context    //返回 valueCtx

providing interception points

暂无更多,待补充

interface 源码分析

说了这么多, 然后可以再来瞧瞧具体源码的实现

interface 底层结构

根据 interface 是否包含有 method,底层实现上用两种 struct 来表示:iface 和 eface。eface表示不含 method 的 interface 结构,或者叫 empty interface。对于 Golang 中的大部分数据类型都可以抽象出来 _type 结构,同时针对不同的类型还会有一些其他信息。

    type eface struct {_type *_typedata  unsafe.Pointer}type _type struct {size       uintptr // type sizeptrdata    uintptr // size of memory prefix holding all 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 Calg        *typeAlg  // algorithm tablegcdata    *byte    // garbage collection datastr       nameOff  // string formptrToThis typeOff  // type for pointer to this type, may be zero}

iface 表示 non-empty interface 的底层实现。相比于 empty interface,non-empty 要包含一些 method。method 的具体实现存放在 itab.fun 变量里。

    type iface struct {tab  *itabdata unsafe.Pointer}// layout of Itab known to compilers// allocated in non-garbage-collected memory// Needs to be in sync with// ../cmd/compile/internal/gc/reflect.go:/^func.dumptypestructs.type itab struct {inter  *interfacetype_type  *_typelink   *itabbad    int32inhash int32      // has this itab been added to hash?fun    [1]uintptr // variable sized}

试想一下,如果 interface 包含多个 method,这里只有一个 fun 变量怎么存呢?
其实,通过反编译汇编是可以看出的,中间过程编译器将根据我们的转换目标类型的 empty interface 还是 non-empty interface,来对原数据类型进行转换(转换成 <_type, unsafe.Pointer> 或者 <itab, unsafe.Pointer>)。这里对于 struct 满不满足 interface 的类型要求(也就是 struct 是否实现了 interface 的所有 method),是由编译器来检测的。

iface 之 itab

iface 结构中最重要的是 itab 结构。itab 可以理解为 pair<interface type, concrete type> 。当然 itab 里面还包含一些其他信息,比如 interface 里面包含的 method 的具体实现。下面细说。itab 的结构如下。

    type itab struct {inter  *interfacetype_type  *_typelink   *itabbad    int32inhash int32      // has this itab been added to hash?fun    [1]uintptr // variable sized}

其中 interfacetype 包含了一些关于 interface 本身的信息,比如 package path,包含的 method。上面提到的 iface 和 eface 是数据类型(built-in 和 type-define)转换成 interface 之后的实体的 struct 结构,而这里的 interfacetype 是我们定义 interface 时候的一种抽象表示。

    type interfacetype struct {typ     _typepkgpath namemhdr    []imethod}type imethod struct {   //这里的 method 只是一种函数声明的抽象,比如  func Print() errorname nameOffityp typeOff}

_type 表示 concrete type。fun 表示的 interface 里面的 method 的具体实现。比如 interface type 包含了 method A, B,则通过 fun 就可以找到这两个 method 的具体实现。

interface的内存布局

了解interface的内存结构是非常有必要的,只有了解了这一点,我们才能进一步分析诸如类型断言等情况的效率问题。先看一个例子:

    type Stringer interface {String() string}type Binary uint64func (i Binary) String() string {return strconv.Uitob64(i.Get(), 2)}func (i Binary) Get() uint64 {return uint64(i)}func main() {b := Binary{}s := Stringer(b)fmt.Print(s.String())}

根据上面interface的源码实现,可以知道,interface在内存上实际由两个成员组成,如下图,tab指向虚表,data则指向实际引用的数据。虚表描绘了实际的类型信息及该接口所需要的方法集

![Uploading interface内存布局_731644.png]

观察itable的结构,首先是描述type信息的一些元数据,然后是满足Stringger接口的函数指针列表(注意,这里不是实际类型Binary的函数指针集哦)。因此我们如果通过接口进行函数调用,实际的操作其实就是s.tab->fun0。是不是和C++的虚表很像?接下来我们要看看golang的虚表和C++的虚表区别在哪里。

先看C++,它为每种类型创建了一个方法集,而它的虚表实际上就是这个方法集本身或是它的一部分而已,当面临多继承时(或者叫实现多个接口时,这是很常见的),C++对象结构里就会存在多个虚表指针,每个虚表指针指向该方法集的不同部分,因此,C++方法集里面函数指针有严格的顺序。许多C++新手在面对多继承时就变得蛋疼菊紧了,因为它的这种设计方式,为了保证其虚表能够正常工作,C++引入了很多概念,什么虚继承啊,接口函数同名问题啊,同一个接口在不同的层次上被继承多次的问题啊等等……就是老手也很容易因疏忽而写出问题代码出来。

我们再来看golang的实现方式,同C++一样,golang也为每种类型创建了一个方法集,不同的是接口的虚表是在运行时专门生成的。可能细心的同学能够发现为什么要在运行时生成虚表。因为太多了,每一种接口类型和所有满足其接口的实体类型的组合就是其可能的虚表数量,实际上其中的大部分是不需要的,因此golang选择在运行时生成它,例如,当例子中当首次遇见s := Stringer(b)这样的语句时,golang会生成Stringer接口对应于Binary类型的虚表,并将其缓存。

理解了golang的内存结构,再来分析诸如类型断言等情况的效率问题就很容易了,当判定一种类型是否满足某个接口时,golang使用类型的方法集和接口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型满足该接口。例如某类型有m个方法,某接口有n个方法,则很容易知道这种判定的时间复杂度为O(mXn),不过可以使用预先排序的方式进行优化,实际的时间复杂度为O(m+n)。

interface 与 nil 的比较

引用公司内部同事的讨论议题,觉得之前自己也没有理解明白,为此,单独罗列出来,例子是最好的说明,如下

package mainimport ("fmt""reflect"
)type State struct{}func testnil1(a, b interface{}) bool {return a == b
}func testnil2(a *State, b interface{}) bool {return a == b
}func testnil3(a interface{}) bool {return a == nil
}func testnil4(a *State) bool {return a == nil
}func testnil5(a interface{}) bool {v := reflect.ValueOf(a)return !v.IsValid() || v.IsNil()
}func main() {var a *Statefmt.Println(testnil1(a, nil))fmt.Println(testnil2(a, nil))fmt.Println(testnil3(a))fmt.Println(testnil4(a))fmt.Println(testnil5(a))
}

返回结果如下

false
false
false
true
true

为啥呢?

一个interface{}类型的变量包含了2个指针,一个指针指向值的类型,另外一个指针指向实际的值
对一个interface{}类型的nil变量来说,它的两个指针都是0;但是var a *State传进去后,指向的类型的指针不为0了,因为有类型了, 所以比较为false。 interface 类型比较, 要是 两个指针都相等, 才能相等。

【"欢迎关注我的微信公众号:Linux 服务端系统研发,后面会大力通过微信公众号发送优质文章"】

image.png

18人点赞

Golang

更多精彩内容,就在简书APP

"欢迎关注我的微信公众号:Linux 服务端系统研发,后面会大力通过微信公众号发送优质文章"

赞赏支持还没有人赞赏,支持一下正在上传…重新上传取消​

吴德宝AllenWu服务端开发、C/C++、Golang、IM、架构、区块链、容器、istio

总资产6共写了6.7W字获得217个赞共223个粉丝

关注

被以下专题收入,发现更多相似内容

正在上传…重新上传取消​Go正在上传…重新上传取消​golang正在上传…重新上传取消​Go知识库正在上传…重新上传取消​我爱编程正在上传…重新上传取消​Golang

推荐精彩内容

  • 无标题文章

    转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....

    正在上传…重新上传取消​40c0490e5268阅读 1,035评论 0赞 9

  • 百日长红——紫薇[26]

    百花诗 26 (紫薇) 娇弱紫衣百日红,害羞怕痒满身风。 夏秋开放显尊贵,好运来临情爱浓。

    正在上传…重新上传取消​PikeTalk阅读 52评论 0赞 1

  • checkebox全选反选成功一次后失效

    最近在项目中使用jQuery的attr方法获取和设置复选框的”checked”属性,发现第一次全选/取消全选有效,...

    正在上传…重新上传取消​邢泽川阅读 220评论 0赞 0

  • 通电程序

    1 . 开总电门,测试警告灯,开 5 灯,开皮托管加温,放襟翼 2 . 收襟翼,关5灯,关皮托管加温,关总电门

    正在上传…重新上传取消​HugSum阅读 85评论 0赞 0

正在上传…重新上传取消​

吴德宝AllenWu

关注

总资产6

程序员基本素养和特质

阅读 139

git workflow 规范

阅读 144

推荐阅读

Go 专栏|接口 interface

阅读 189

java8(二)lambda表达式手把手学习

阅读 478

成员变量

阅读 247

1、JavaOOP面试题(98题)

阅读 127

浅谈前端AST的概念与实际应用

阅读 373

评论1

赞18

Golang interface 全面介绍相关推荐

  1. golang 微信支付介绍

    golang微信支付介绍 本次只介绍单个普通商户支付功能 下载微信提供的第三方包wechatpay-go 下载地址 https://github.com/wechatpay-apiv3/wechatp ...

  2. Golang interface 接口详细原理和使用技巧

    文章目录 Golang interface 接口详细原理和使用技巧 一.Go interface 介绍 interface 在 Go 中的重要性说明 interface 的特性 interface 接 ...

  3. golang基础-etcd介绍与使用、etcd存取值、etcd监测数据写入

    关注公众号"风色年代"订阅更多精彩文章,本博大部分文章为转载并已标明原文出处,如有再转敬请保留,请自觉尊重原创作者的劳动成果! golang基础-etcd介绍与使用.etcd存取值 ...

  4. Java Debug Interface(JDI)介绍和使用JDI调试程序

    Java Debug Interface(JDI)介绍 An Intro to the Java Debug Interface (JDI) | Baeldung 1. 概述 我们可能会想像Intel ...

  5. golang interface 转 int string slice struct 类型

    在golang中,interface{}允许接纳任意值,int, string, struct,slice等,因此我可以很简单的将值传递到interface{} package main import ...

  6. Java里的接口的interface 简单介绍.

    这是写给我自己和我这种初学者看的. Java作为1个强面向对象语言,  基本上所有东西(成员和方法)都是写在class(类)里面的. 但是也存在一种与class平行的东西, 它就是interface ...

  7. golang interface 类型转换_无符号Golang程序逆向方法解析

    在去年的inctf2018中,出现了一道Go语言编写的进程通信逆向题,无论是从题目整体设计还是解题思路上来说都独树一帜,自己在解题过程中遇到了很多问题,但我这不打算做过多探讨,网上也有大佬的解题过程, ...

  8. golang协程介绍和理解

    1.介绍协程前先需要了解进程和线程 进程:程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位. 线程:进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基 ...

  9. Golang笔记——json介绍

    json 基本介绍 概述 应用场景(示意图) json 数据格式说明 json 数据在线解析 https://www.json.cn/ 网站可以验证一个 json 格式的数据是否正确.尤其是在我们编写 ...

  10. golang interface 类型转换_Golang面试题41道

    Golang面试题41道 大家好,这一期呢,我们来说一下golang的面试题. 第1题什么是golang? go是一个开源的编程语言,由谷歌开发的.这门语言是设计用来做系统级的编程的. 第2题为什么要 ...

最新文章

  1. 微型计算机分析,微机原理练习题分析
  2. 【组合数学】生成函数 ( 使用生成函数求解不定方程解个数 )
  3. 日常生活中怎样利用计算机的,数据存储与管理在日常生活中的三种方式
  4. windows找不到文件javaw_windows电脑上,怎么快速找文件?
  5. 利用 git format-patch 和 git send-email 把修改的 patch 文件发送给 ffmpeg-devel
  6. 十问十答 CDDL 许可证
  7. 在Visual Studio 2012中使用VMSDK开发领域特定语言(二)
  8. 微信小程序开发学习笔记007--微信小程序项目01
  9. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?...
  10. chrome浏览器无法登录印象笔记
  11. 汽车行业(车厂)常见英文缩写及其中文含义(不断完善中)
  12. 【ChatGPT】GPT-4
  13. blender 2.8的基本使用和使用形态键(Shape key)做帧动画
  14. 9行代码用python制作迷宫gif动画
  15. TestBird成为全球最大手游测试平台
  16. performance API 中什么指标可以衡量首屏时间
  17. 碳纤维复合材料加固钢筋混凝土的极化作用
  18. 使用GHOST镜像文件在VMWARE WROKSTATION上安装系统(图)
  19. 用“牛顿迭代法”求根号2的近似值
  20. Linux 多线程下载工具 axel 下载加速器

热门文章

  1. 敌兵布阵——线段树单点修改区间查询
  2. 使用OEM复制数据库
  3. PCIe总线的参考时钟与同步时钟的差异
  4. mysql 中caption_ACCESS数据库中Field对象的caption属性读写
  5. matlab 秩和检验,多个独立样本比较的秩和检验(Kruskal-Wallis H)
  6. 网易To B的差异化路线
  7. 本地化ASP.NET core模型绑定错误消息
  8. Win系统 - 该死!WebGL 遇到了问题 (chrome浏览器)
  9. 别了,Easy微博!
  10. python创意网络爬虫_python之网络爬虫