文章目录

  • GoLang之interface底层系列二(类型断言)
    • 1.抽象类型、具体类型
    • 2.断言的作用类型与目标类型
    • 3.空接口.(具体类型)
    • 4.非空接口.(具体类型)
    • 5.空接口.(非空接口)
    • 6.非空接口.(非空接口)
    • 7.Type Switch
    • 8.附

GoLang之interface底层系列二(类型断言)

注:本文基于Windos系统上Go SDK v1.18进行讲解

1.抽象类型、具体类型

我们已经知道接口可以分为空接口与非空接口两类。相对于接口这种“抽象类型”,像int,slice,string,map,struct等类型被称为“具体类型”。
类型断言是Go语言中应用在接口值上的一个神奇特性,而类型断言的目标类型可以是某种具体类型,也可以是某种非空接口类型。这样我们就组合出四种类型断言,接下来就逐一看看它们究竟是如何断言的!

2.断言的作用类型与目标类型

类型断言作用在接口值之上,可以是空接口,也可以是非空接口;

而断言的目标类型,可以是具体类型,也可以是非空接口类型。这样就组合出了四种类型断言,接下来我们就逐一看看,它们究竟是怎样断言的。

3.空接口.(具体类型)

e.(*os.File)就是要判断e存储的_type是否指向*os.File的类型元数据。反正我们介绍过Go语言里每种类型的类型元数据都是全局唯一的。

var e interface{}
//......
r, ok := e.(*os.File)
fmt.Println(r, ok) //<nil> false

如果e像下面这样赋值,,e的动态值就是f,e的动态类型就是*os.File,所以e.(*os.File)类型断言成功,ok为true,r为e的动态值,r被赋值为e的动态值f

//eggo.txt存在
func main() {var e interface{}f, _ := os.Open("aaa.txt")fmt.Println(f)//输出:&{0xc0000d0780}e = fr, ok := e.(*os.File)fmt.Println(ok) //输出:truefmt.Println(r)  //输出:&{0xc0000d0780}
}
//eggo.txt不存在var e interface{}f, _ := os.Open("eggo.txt")e = fr, ok := e.(*os.File)fmt.Println(f)        //<nil>fmt.Println(f == nil) //truefmt.Println(e)        //<nil>fmt.Println(e == nil) //flasefmt.Println(r, ok)    //<nil> true


如果e的动态类型不是*os.File,例如e被赋值为string,e的动态类型就是string,那么类型断言就会失败,ok就是false,r就是*os.File的类型零值nil。

var e interface{}
f := "eggo"
e = f
r, ok := e.(*os.File)
fmt.Println(r, ok) //<nil> false



4.非空接口.(具体类型)

rw.(*os.File)是要判断rw的动态类型是否为*os.File。前面我们介绍过,程序中用到的itab结构体都会缓存起来,可以通过<接口类型, 动态类型>组合起来的key,查找到对应的itab指针。
所以这里的类型断言只需要一次比较就能完成,就是看iface.tab是否等于<io.ReadWriter, *os.File>这个组合对应的itab指针就好。

    var rw io.ReadWriterfmt.Println(rw)//<nil>//......r, ok := rw.(*os.File)fmt.Println(r, ok) //<nil> false

如果把一个*os.File类型的变量f赋给rw,它的动态值就是f,动态类型就是*os.File。
rw这里存储的itab指针就指向<io.ReadWriter, *os.File>组合对应的itab,所以类型断言成功,ok为true,r被赋值为rw的动态值。

    //eggo.txt存在var rw io.ReadWriterfmt.Println(rw)f, _ := os.Open("eggo.txt") //<nil>fmt.Println(f)              //&{0xc00006c000}rw = ffmt.Println(rw) //&{0xc00006c000}r, ok := rw.(*os.File)fmt.Println(r, ok) //&{0xc00006c000} true
    //eggo.txt不存在var rw io.ReadWriterfmt.Println(rw)f, _ := os.Open("eggo.txt") //<nil>fmt.Println(f)//<nil>rw = ffmt.Println(rw)//<nil>r, ok := rw.(*os.File)fmt.Println(r, ok) //<nil> true


下面我们定义一个eggo类型,并且由*eggo类型实现io.ReadWriter接口。
如果把一个*eggo类型的变量赋值给rw,rw的动态类型就是*eggo,rw持有的itab指针就不指向<io.ReadWriter, *os.File>组合对应的itab结构体,类型断言就会失败,ok为false,而r就会被置为*os.File的类型零值nil。

package mainimport ("fmt""io""os"
)type eggo struct {name string
}func (e *eggo) Read(b []byte) (n int, err error) {return len(e.name), nil
}func (e *eggo) Write(b []byte) (n int, err error) {return len(e.name), nil
}
func main() {var rw io.ReadWriterf := eggo{name: "eggo"}fmt.Println(f) //{eggo}rw = &ffmt.Println(rw) //&{eggo}r, ok := rw.(*os.File)fmt.Println(r, ok) //<nil> false
}


package mainimport ("fmt""io"
)type eggo struct {name string
}func (e *eggo) Read(b []byte) (n int, err error) {return len(e.name), nil
}func (e *eggo) Write(b []byte) (n int, err error) {return len(e.name), nil
}
func main() {var rw io.ReadWriterf := eggo{name: "eggo"}rw = &fr, ok := rw.(*eggo)fmt.Println(r, ok) //&{eggo} true
}

5.空接口.(非空接口)

e.(io.ReadWriter)就是要判断e的动态类型是否实现了io.ReadWriter接口。

    var e interface{}//......rw, ok := e.(io.ReadWriter)fmt.Println(rw, ok) //<nil> false

如果e像这样赋值:
它的动态值就是f,e的动态类型就是*os.File,我们知道*os.File类型元数据的后面可以找到该类型实现的方法列表描述信息。

    //eggo.txt存在var e interface{}f, _ := os.Open("aaa.txt")fmt.Println(f)//输出:&{0xc000118780}e = f //把*结构体赋值给了空接口rw, ok := e.(io.ReadWriter)fmt.Println(rw) //输出:&{0xc000118780}fmt.Println(ok) //输出:true
//eggo.txt不存在var e interface{}f, _ := os.Open("eggo.txt")fmt.Println(f)//<nil>e = ffmt.Println(e)//<nil>rw, ok := e.(io.ReadWriter)fmt.Println(rw, ok) //<nil> true


其实并不需要每次都检查动态类型的方法列表,还记得itab缓存吗? 实际上,当类型断言的目标类型为非空接口时,会首先去itabTable里查找对应的itab指针,若没有找到io.ReadWrite和os.File对应的itab结构体,再去检查动态类型的方法列表,即再去检查*os.File的方法列表。
此处注意,就算从itabTable中找到了itab指针,也要进一步确认itab.fun[0]是否等于0。这是因为一旦通过方法列表确定某个具体类型没有实现指定接口,就会把itab这里的fun[0]置为0,然后同样会把这个itab结构体缓存起来,和那些断言成功的itab缓存一样。这样做的目的是避免再遇到同种类型断言时重复检查方法列表。
即断言失败的类型组合其对应的itab结构体也会被缓存起来,只是会把itab.fun[0]置为0,用以标识这里的动态类型并没有实现对应的接口,这样以后在遇到同样类型断言时,就不用再去检查方法列表了,可以直接断言失败;
回到例子中,这里会断言成功,ok为true,rw就是一个io.ReadWriter类型的变量,其动态值与e相同。tab指向<io.ReadWriter, *os.File>对应的itab结构体。

然而如果把一个字符串赋值给e,它的动态类型就是string,<io.ReadWriter, string>这个组合会对应下面这个itab,它也会被缓存起来。

 var e interface{}f := "eggo"e = frw, ok := e.(io.ReadWriter)fmt.Println(rw, ok) //< nil > false


断言失败,ok为false,rw为io.ReadWriter的类型零值,即tab和data均为nil。

总结:
下面中代码中断言是成功的,所以ok为true,rw就是一个io.ReadWriter接口类型的变量(即*os.file结构体实现了io.ReadWriter接口),rw的动态值与e相同,而tab就指向这一个itab结构体

如果e被赋值为一个字符串,它的动态类型就是string,并没有实现要求的Read和Write方法,所以对应的itab中fun[0]=0,并且会被添加到itab缓存中,这里断言失败,ok为false,rw为io.ReadWriter的类型零值

6.非空接口.(非空接口)

w.(io.ReadWriter)是要判断w的动态类型是否实现了io.ReadWriter接口。

var w io.Writer
//......
rw, ok := w.(io.ReadWriter)
fmt.Println(rw, ok) //< nil > false

下面同样把一个*os.File类型的变量f赋值给w,它的动态值就是f,动态类型就是*os.File。

//eggo.txt存在var w io.Writerfmt.Println(w) //<nil>f, _ := os.Open("eggo.txt")fmt.Println(f) //&{0xc00011a780}w = ffmt.Println(w) //&{0xc00011a780}rw, ok := w.(io.ReadWriter)fmt.Println(rw, ok) //&{0xc00011a780}  true
 //eggo.txt不存在var w io.Writerfmt.Println(w) //<nil>f, _ := os.Open("eggo.txt")fmt.Println(f) //<nil>w = ffmt.Println(w) //<nil>rw, ok := w.(io.ReadWriter)fmt.Println(rw, ok) //<nil>  true

要确定*os.File是否实现了io.ReadWriter接口,同样会先去itab缓存里查找<io.ReadWriter,*os.File>对应的itab,若存在,且itab.fun[0]不等于0,则断言成功;若不存在,再去检查*os.File的方法列表,创建并缓存itab信息。
这里断言成功,ok为true,rw为io.ReadWriter类型的变量,动态值与w相同,而itab是<io.ReadWriter, *os.File>对应的那一个。


下面我们自定义一个eggo类型,且*eggo类型只实现io.Writer要求的Write方法,并没有实现io.ReadWriter额外要求的Read方法。如果把一个*eggo类型的变量赋给w:

type eggo struct {name string
}func (e *eggo) Write(b []byte) (n int, err error) {return len(e.name), nil
}func main() {var w io.Writerf := eggo{name: "eggo"}w = &frw, ok := w.(io.ReadWriter)fmt.Println(rw, ok) //<nil> false
}

此时,w的动态类型为*eggo,而*eggo的方法列表里缺少一个Read方法,所以类型断言失败,下面这个itab被缓存起来。


断言失败后,ok为false,rw的data和tab均为nil。

综上,类型断言的关键是明确接口的动态类型,以及对应的类型实现了哪些方法。而明确这些的关键,还是类型元数据,以及空接口与非空接口的数据结构。接下来的Type Switch也不外如是。

7.Type Switch

这里的b会被赋值为e的动态值,下面每个case都是把e的动态类型和某个具体类型作比较,相等则选择这个分支,没有匹配的则走到default分支。

var e interface{}
str := "eggo"
e = strswitch b := e.(type) {case *os.File:{fmt.Println("*os.File")}
case string:{fmt.Println(b)    //选择这个分支}
default:fmt.Println("default")
}

有时会遇到多个类型放在一个分支的情况,这时b的类型是interface{}。

switch b := e.(type) {case *os.File:{fmt.Println("这里b的类型为*os.File", b)}
case string:{fmt.Println("这里b的类型为string", b)}
case int, int32, int64:{fmt.Println("多类型分支里b的类型为interface{}", b)}
default:fmt.Println("default")
}

8.附

空接口.(空接口):

type m interface{}func main() {var a interface{}k, ok := a.(m)fmt.Println(k, ok) //<nil> false
}
func main() {var a interface{}k, ok := a.(interface{})fmt.Println(k, ok) //<nil> false
}

非空接口.(空接口):

type a interface{}func main() {var rw io.ReadWriterk, ok := rw.(a)fmt.Println(k, ok)//<nil> false
}

func main() {var rw io.ReadWriterk, ok := rw.(interface{})fmt.Println(k, ok) //<nil> false
}

GoLang之interface底层系列二(类型断言)相关推荐

  1. GoLang之map底层系列二(浅显学习)

    文章目录 GoLang之map底层系列二(浅显学习) 1.map++ 2.map引用传递 3.map不是并发安全 4.map的value为空接口 5.map的value为切片 6.value,ok=m ...

  2. Go 类型转换、类型断言与类型选择

    文章目录 1.类型转换 2.类型断言 3.类型选择 参考文献 Go 是一种静态类型的编译型语言,每一个变量在使用时都有明确的类型与之对应.Go 中,关于数据类型,主要有三点容易让人迷惑,分别是类型转换 ...

  3. golang 接口的底层实现

    Golang将interface作为一种类型,并且不依赖继承,而是以一种类似于duck-typing的实现.所谓duck-typing,是一种动态类型风格:当一个obj走起来像鸭子.游泳起来像鸭子.叫 ...

  4. golang学习笔记:Interface类型断言详情

    原文链接:https://www.2cto.com/kf/201712/703563.html 1. 用于判断变量类型 demo如下: switch t := var.(type){case stri ...

  5. Go 学习笔记(35)— Go 接口 interface (接口声明、接口初始化、接口方法调用、接口运算、类型断言、类型查询、空接口)

    1. 接口概念 接口是双方约定的一种合作协议.接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节.接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式.类型及结构. 接口内部存 ...

  6. golang: 类型转换和类型断言

    类型转换在程序设计中都是不可避免的问题.当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关注这方面的问题.但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动转换,编译器不会 ...

  7. golang 获取struct类型_聊聊golang的类型断言

    序 本文主要研究一下golang的类型断言 类型断言 x.(T) 断言x不为nil且x为T类型 如果T不是接口类型,则该断言x为T类型 如果T类接口类型,则该断言x实现了T接口 实例1 func ma ...

  8. golang类型断言的使用(Type Assertion)

    第一部分 首先,转自https://studygolang.com/articles/3314对断言的基本介绍 golang的语言中提供了断言的功能.golang中的所有程序都实现了interface ...

  9. Golang——接口、多态、接口继承与转换、空接口、类型断言

    接口是一种规范与标准,只是规定了要做哪些事情.但是具体怎么做,是实现接口的类去做的,接口只是把所有具有共性的方法定义在一起. 接口存在的意义就是用来定义规范,用来做功能的拓展 接口最大的好处是可以实现 ...

最新文章

  1. MySQL数据库从入门到实战(四)
  2. CVPR 2019 ATOM:《ATOM: Accurate Tracking by Overlap Maximization》论文笔记
  3. mysql scrapy 重复数据_MySQL大数据量表中删除重复记录
  4. 图像转svg,及绘制svg图像
  5. [BZOJ3297][USACO2011 Open]forgot
  6. android studio 导入c,3.3、Android Studio 添加 C 和 C++ 项目
  7. deepstream imagedata multistream 中文注释数据流
  8. Android和ipad同步短信,Android通信录和短信转移到iphone的方法-QQ同步助手
  9. 自定义加密cookie加盐密码
  10. 第1章第25节:如何通过幻灯片母版统一管理相同类型的幻灯片1 [PowerPoint精美幻灯片实战教程]
  11. 毕业设计开发日志,基于ARM的嵌入式人脸识别系统的设计与实现
  12. java linux路径带括号,java执行linux命令 括号
  13. 火车头采集器使用‘导入数据库’形式发布数据到帝国CMS数据库的过程(原创)适用于ECMS7.2
  14. 物联网云平台将成为产业生态的核心
  15. 台式计算机中的CPU指的是,电脑硬件认识之什么是电脑的CPU(cpu详细介绍)
  16. 图像处理与计算机视觉基础相关领域的经典书籍以及论文
  17. STM32如何下载官方固件库
  18. c语言调幅度程序,广播监测设备入网技术要求及测量方法(DOC 72页).doc
  19. Executor及Executors
  20. request.setAttribute理解

热门文章

  1. DHT网络 学习笔记
  2. mysql怎么设置每天定时清表_Mysql每天定时清空表
  3. SDUT-OJ偏序关系
  4. js自定义提示框弹窗
  5. NDIS驱动学习笔记
  6. Kylin快速入门系列(1) | Kylin的简单介绍及安装部署
  7. js处理blur事件触发多次
  8. redis安装和启动
  9. Redis 学习 - 05 Node.js 客户端操作 Redis、Pipeline 流水线
  10. maxEms和maxLength的区别以及maxEms不起作用的问题