侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Go之interface的具体使用

Go  /  管理员 发布于 5年前   348

浅显地了解了一下 Go,发现 Go 语法的设计非常简洁,易于理解。正应了 Go 语言之父 Rob Pike 说的那句“Less is more”―― 大道至简。

下面就具体的语法特性说说我自己的体会。

interface

概览

与通常以类型层次与继承为根基的面向对象设计(OOP)语言(如C++、Java)不同,Go 的核心思想就是组合(composition)。Go 进一步解耦了对象与操作,实现了真正的鸭子类型(Duck typing):一个对象如果能嘎嘎叫那就能当做鸭子,而不是像 C++ 或 Java 那样需要类型系统去保证:一个对象先得是只鸭子,然后才能嘎嘎叫。

type Duck interface {  Quack()}type Animal struct {  name string}func (animal Animal) Quack() {  fmt.Println(animal.name, ": Quack! Quack! Like a duck!")}func main() {  unknownAnimal := Animal{name: "Unknown"}  var equivalent Duck  equivalent = unknownAnimal  equivalent.Quack()}

运行上面的代码输出:

Unknown : Quack! Quack! Like a duck!

下面用 Java 语言来实现:

interface Duck {  void Quack();}class SomeAnimal implements Duck {  String name;  public SomeAnimal(String name) {    this.name = name;  }  public void Quack() {    System.out.println(name + ": Quack! Quack! I am a duck!");  }}public class Test {  public static void main(String []args){    SomeAnimal unknownAnimal = new SomeAnimal("Unknown");    Duck equivalent = unknownAnimal;    equivalent.Quack();  }}

两相比较就能看出:Go 将对象与对其的操作(方法或函数)解耦得更彻底。Go 并不需要一个对象通过类型系统来保证实现了某个接口(is a),而只需要这个对象实现了某个接口的方法即可(like a),而且类型声明与方法声明或实现也是松耦合的形式。如果稍微转换一下方法的实现方式:

func (animal Animal) Quack() {  fmt.Println(animal.name, ": Quack! Quack! Like a duck!")}

为:

func Quack(animal Animal) {  fmt.Println(animal.name, ": Quack! Quack! Like a duck!")}

是不是就和普通方法并无二致了?

在深入浅出 Cocoa 之消息一文中我曾分析过 Objective C 的消息调用过程:

Bird * aBird = [[Bird alloc] init];[aBird fly];

中对 fly 的调用,编译器通过插入一些代码,将之转换为对方法具体实现 IMP 的调用,这个 IMP 是通过在 Bird 的类结构中的方法链表中查找名称为 fly 的选择子 SEL 对应的具体方法实现找到的,编译器会将消息调用转换为对消息函数 objc_msgSend的调用:

objc_msgSend(aBird, @selector(fly));

无论是 Objective C 的消息机制还是 Qt 中的 Signal/Slot 机制,可以说都是在尝试将对象本身(数据)与对对象的操作(消息)解耦,但 Go 将这个工作在语言层面做得更加彻底,这样不仅避免多重继承问题,还体现出面向对象设计中最要紧的事情:对象间的消息传递。

实现

interface 实际上就是一个结构体,包含两个成员。其中一个成员是指向具体数据的指针,另一个成员中包含了类型信息。空接口和带方法的接口略有不同,下面分别是空接口和带方法的接口是使用的数据结构:

struct Eface{  Type*  type;  void*  data;};struct Iface{  Itab*  tab;  void*  data;};struct Itab{  InterfaceType*  inter;  Type*  type;  Itab*  link;  int32  bad;  int32  unused;  void  (*fun[])(void);};struct Type{  uintptr size;  uint32 hash;  uint8 _unused;  uint8 align;  uint8 fieldAlign;  uint8 kind;  Alg *alg;  void *gc;  String *string;  UncommonType *x;  Type *ptrto;};

先看Eface,它是interface{}底层使用的数据结构。数据域中包含了一个void*指针,和一个类型结构体的指针。interface{}扮演的角色跟C语言中的void*是差不多的,Go中的任何对象都可以表示为interface{}。不同之处在于,interface{}中有类型信息,于是可以实现反射。

不同类型数据的类型信息结构体并不完全一致,Type是类型信息结构体中公共的部分,其中size描述类型的大小,UncommonType是指向一个函数指针的数组,收集了这个类型的具体实现的所有方法。

在reflect包中有个KindOf函数,返回一个interface{}的Type,其实该函数就是简单的取Eface中的Type域。

Iface和Eface略有不同,它是带方法的interface底层使用的数据结构。data域同样是指向原始数据的,Itab中不仅存储了Type信息,而且还多了一个方法表fun[]。一个Iface中的具体类型中实现的方法会被拷贝到Itab的fun数组中。

Type的UncommonType中有一个方法表,某个具体类型实现的所有方法都会被收集到这张表中。reflect包中的Method和MethodByName方法都是通过查询这张表实现的。表中的每一项是一个Method,其数据结构如下:

struct Method{  String *name;  String *pkgPath;  Type  *mtyp;  Type *typ;  void (*ifn)(void);  void (*tfn)(void);};

Iface的Itab的InterfaceType中也有一张方法表,这张方法表中是接口所声明的方法。其中每一项是一个IMethod,数据结构如下:

struct IMethod{  String *name;  String *pkgPath;  Type *type;};

 跟上面的Method结构体对比可以发现,这里是只有声明没有实现的。

Iface中的Itab的func域也是一张方法表,这张表中的每一项就是一个函数指针,也就是只有实现没有声明。

类型转换时的检测就是看Type中的方法表是否包含了InterfaceType的方法表中的所有方法,并把Type方法表中的实现部分拷到Itab的func那张表中。

注意事项

一个interface在没有进行初始化时,对应的值是nil。也就是说:

var v interface{}

此时v就是一个nil。在底层存储上,它是一个空指针。

与之不同的情况

var obj *Tvar v interface{}v = obj

此时v是一个interface,它的值是nil,也就是说其data域为空,但它自身不为nil。

下面来看个例子就明白了:
Go语言中的error类型实际上是抽象了Error()方法的error接口:

type error interface {  Error() string}

有如下代码:

type Error struct {  errCode uint8}func (e *Error) Error() string {  switch e.errCode {  default:    return "unknown error"  }}func test_checkError() {  var e *Error  if e == nil {    fmt.Println("e is nil")  } else {    fmt.Println("e is not nil")  }  var err error  err = e  if err == nil {    fmt.Println("err is nil")  } else {    fmt.Println("err is not nil")  }}

运行test_checkError()输出:

e is nil
err is not nil

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    Go语言中slice作为参数传递时遇到的一些坑
    下一条:
    Go语言实现自动填写古诗词实例代码
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2017-09
    • 2020-03
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-12
    • 2021-01
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    • 2024-08
    • 2024-11
    • 2025-02
    • 2025-04
    • 2025-05
    • 2025-06
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客