类型断言

文章目录

  • 类型断言
    • 空接口.(具体类型)
    • 非空接口.(具体类型)
    • 空接口.(非空接口)
    • 非空接口.(非空接口)
    • Type Switch

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

空接口.(具体类型)

var e interface{}
//......
r,ok := e.(*os.File)

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

f,_ := os.Open("hello.txt")
e = f

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

f := "eggo"
e = f


请看以下代码的演示:

package mainimport ("fmt""os"
)func main()  {var e interface{}   //e为空接口类型r, ok := e.(*os.File)  //将e与*os.File类型进行类型断言fmt.Println(r)fmt.Println(ok)fmt.Println()f, _ := os.Open("hello.txt")e = f    //将*os.File类型的f赋值给er, ok = e.(*os.File)fmt.Println(r)fmt.Println(ok)fmt.Println()f1 := "hello"e = f1  //将string类型的f1赋值给er, ok = e.(*os.File)fmt.Println(r)fmt.Println(ok)fmt.Println()
}

执行go run以后的结果:

<nil>
false&{0xc000088780}
true<nil>
false

非空接口.(具体类型)

var rw io.ReadWriter
//......
r,ok := rw.(*os.File)

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

如果把一个os.File类型的变量f赋给rw,它的动态值就是f,动态类型就是os.File。

f,_ := os.Open("hello.txt")
rw = f

rw这里存储的itab指针就指向<io.ReadWriter, *os.File>组合对应的itab,所以类型断言成功,ok为true,r被赋值为rw的动态值。


下面我们定义一个eggo类型,并且由*eggo类型实现io.ReadWriter接口。

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
}

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

f := eggo{name: "eggo"}
rw = &f

请看以下代码的演示:

package mainimport ("fmt""io""os"
)// 定义一个eggo类型
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.ReadWriterr, ok := rw.(*os.File)fmt.Println(r)fmt.Println(ok)fmt.Println()f, _ := os.Open("hello.txt")rw = fr, ok = rw.(*os.File)fmt.Println(r)fmt.Println(ok)fmt.Println()f1 := eggo{name: "eggo"}rw = &f1r, ok = rw.(*os.File)fmt.Println(r)fmt.Println(ok)fmt.Println()
}

执行go run以后的运行结果:

<nil>
false<nil>
true<nil>
false

空接口.(非空接口)

var e interface{}
//......
rw,ok := e.(io.ReadWriter)

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

如果e像这样赋值:

f,_ := os.Open("hello.txt")
e = f

e的动态类型就是os.File,我们知道os.File类型元数据的后面可以找到该类型实现的方法列表描述信息。

其实并不需要每次都检查动态类型的方法列表,还记得itab缓存吗? 实际上,当类型断言的目标类型为非空接口时,会首先去itabTable里查找对应的itab指针,若没有找到,再去检查动态类型的方法列表。
此处注意,就算从itabTable中找到了itab指针,也要进一步确认itab.fun[0]是否等于0。这是因为一旦通过方法列表确定某个具体类型没有实现指定接口,就会把itab这里的fun[0]置为0,然后同样会把这个itab结构体缓存起来,和那些断言成功的itab缓存一样。这样做的目的是避免再遇到同种类型断言时重复检查方法列表。

回到例子中,这里会断言成功,ok为true,rw就是一个io.ReadWriter类型的变量,其动态值与e相同。tab指向<io.ReadWriter, *os.File>对应的itab结构体。

f := "eggo"
e = f

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

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

示例请看以下代码:

package mainimport ("fmt""io""os"
)func main()  {var e interface{}rw, ok := e.(io.ReadWriter)fmt.Println(rw)fmt.Println(ok)fmt.Println()f, _  := os.Open("hello.txt")e = frw, ok = e.(io.ReadWriter)fmt.Println(rw)fmt.Println(ok)fmt.Println()f1 := "eggo"e = f1rw, ok = e.(io.ReadWriter)fmt.Println(rw)fmt.Println(ok)fmt.Println()
}

执行```go run``以后的运行结果:

<nil>
false&{0xc000108780}
true<nil>
false

非空接口.(非空接口)

var w io.Writer
//......
rw,ok := w.(io.ReadWriter)

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

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

f,_ := os.Open(“hello.txt”)
w = f

要确定*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
}f := eggo{name: "eggo"}
w = &f

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

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


示例请看以下代码:

package mainimport ("fmt""io""os"
)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.Writerrw, ok := w.(io.ReadWriter)fmt.Println(rw)fmt.Println(ok)fmt.Println()f, _ := os.Open("hello.txt")w = frw, ok = w.(io.ReadWriter)fmt.Println(rw)fmt.Println(ok)fmt.Println()f1 := eggo{name: "eggo"}w = &f1rw, ok = w.(io.ReadWriter)fmt.Println(rw)fmt.Println(ok)fmt.Println()
}

执行go run以后的结果:

<nil>
false&{0xc000108780}
true<nil>
false

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

Type Switch

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会被赋值为e的动态值,下面每个case都是把e的动态类型和某个具体类型作比较,相等则选择这个分支,没有匹配的则走到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")
}

参考资料:

https://mp.weixin.qq.com/s/i0vmHjF7faDo0hvOlVfJcA

golang 类型断言相关推荐

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

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

  2. golang类型断言理解[go语言圣经]

    类型断言 个人理解 这个东西看了好久才看懂,然后还借鉴了一下其他的文章: Go 语言中的类型断言是什么? 虽然这个借鉴的文章没有讲全,讲透,但是让我理解go语言圣经里面写的内容起到了帮助 我的理解关键 ...

  3. golang类型断言及检测其是否断言成功

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

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

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

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

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

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

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

    文章目录 GoLang之interface底层系列二(类型断言) 1.抽象类型.具体类型 2.断言的作用类型与目标类型 3.空接口.(具体类型) 4.非空接口.(具体类型) 5.空接口.(非空接口) ...

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

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

  9. Golang笔记-面向对象编程-多态/类型断言

    面向对象编程-多态 基本介绍 变量(实例)具有多种形态.面向对象的第三大特征,在 Go 语言,多态特征是通过接口实现的.可 以按照统一的接口来调用不同的实现.这时接口变量就呈现不同的形态. 快速入门 ...

最新文章

  1. Node.js中的express框架,修改内容后自动更新(免重启),express热更新
  2. 2.8 使用开源的实现方案-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  3. Java 面向对象的特征---学习笔记
  4. 两个单链表相交的一系列问题----0_0
  5. CSS学习笔记--CSS语法与选择器
  6. Mail: JMail, System.Net.Mail, System.Web.Mail
  7. 乾颐堂军哥HCNP真题(科目221)解析,含2017年最新更新后题库,61到75题
  8. 【JVM · 调优】监控及诊断工具
  9. 单点登录系统CAS入门
  10. vue实现标题导航,tab选项卡(一看就会)
  11. Android Intent详解
  12. css实现毛玻璃效果——backdrop-filter
  13. 一场“测谎”人机对战背后的故事:度小满的技术进击之路
  14. java中的throw_java 中Throw能抛出的是什么?
  15. mysql 批量查询
  16. ❤️数据结构之栈(图文版详解)❤️
  17. 从零开始在 FreeNAS 的 Jail 上安装 NextCloud 并配置 Nginx 作为网页服务器而且 设置SSL证书 使用 https 访问 以及 oc_filecache 修复方法、优化方法
  18. Acer 4750 安装黑苹果_黑苹果全套安装教程!
  19. 说一说实战项目升级从vue2到vue3 之main.js 区别
  20. steam如何一键登录_如何在Steam上启用一键通

热门文章

  1. java poi 图片 内存溢出_解决java poi海量数据导出内存溢出问题
  2. 解决 java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts报错
  3. 郭敬明的最伤感经典125句
  4. 发几个很二的笑话让大家笑一下!笑一笑,十年少.
  5. C# winform中listview排序
  6. 金仓数据库KingbaseES数据类型和oracle数据类型的映射表
  7. 南京市秦淮区经济和信息化局局长严敬与OPPO资深通信标准专家许华莅临云创
  8. 《财富》中文版:2005年酷公司
  9. Kettle(五)分页抽取、插入数据
  10. React 集成 ECharts实现柱状图、折线图、饼状图