

Golang 避坑指南(一):interface 之坑多多
source link: https://mp.weixin.qq.com/s/bWsg-ZDBTp3Fp_3Lik83oA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

点击上方蓝色“ Go语言中文网 ”关注我们, 领全套Go资料 ,每天学习 Go 语言
interface{}和 void*
/Object 是一样的吗?
先来看一段关于 interface 的官方说明
Under the covers, interfaces are implemented as two elements, a type and a value. The value, called the interface’s dynamic value, is an arbitrary concrete value and the type is that of the value. For the int value 3, an interface value contains, schematically, (int, 3).
事实上,interface 的实现分为两种 eface,iface,它们结构如下:
type iface struct { tab *itab data unsafe.Pointer } type eface struct { _type *_type data unsafe.Pointer }
iface 为有方法声明的 interface,eface 为空的 interface 即 interface{}。我们可以看到 eface 结构体中只存两个指针:一个_type 类型指针用于存数据的实际类型,一个通用指针(unsafe.Pointer)存实际数据;iface 则比较复杂,这里不做详细展开了,你只需要记住它也是两个指针,和 eface 一样其中一个用来存数据,另一个 itab 指针用来存数据类型以及方法集。因此 interface 类型的变量所占空间一定为 16。
明白了原理,我们看一段简单代码:
example1
type Student struct { Name string } var b interface{} = Student{ Name: "aaa", } var c = b.(Student) c.Name = "bbb" fmt.Println(b.(Student).Name)
你觉得输出是什么?
如果你的答案是 bbb,恭喜你,你掉坑了!不信你运行试试:
//example1 output aaa
这个坑道理很简单,根据我们开头讲的 interface 原理,很多从 java 或 c/c++转过来的程序员都把 interface{}看成了 Object 或`void*``。
的确,很多场景(如传参、返回值)它们的确很类似;但请注意,在底层实现上它们是完全不同的。java 的 Object 以及 c 语言的 void*
可以通过通过强转为某个类型获取指向原数据的一个目标类型的引用或指针,因此如果在这个引用或指针上进行修改操作,原数据也会被修改;但是 golang 的 interface 和具体类型之间的转换、赋值是将实际数据复制了一份进行操作的。例如上例中的
var c = b.(Student)
实际的过程是首先将 b 指向的数据复制一份,然后转换为 Student 类型赋值给 c。
记住了吗?好,我们看个类似的例子:
example2
type Student struct { Name string } a := Student{Name:"aaa"} var b interface{} = a a.Name = "bbb" fmt.Println(b.(Student).Name)
这次,输出结果又会是什么?
如果你给出的答案是 aaa,恭喜你脱坑了!
遇到 interface 类的返回值你要注意了
看个简短的例子:
example3
func GetReader(id int64) io.Reader { var r *MyReader = nil if id > 0 && id < 10000{ r = openReader(id) } return r } func main() { r := GetReader(-2) if r == nil { fmt.Println("bad reader") } else { fmt.Println("valid reader") } }
其中 MyReader 为某个实现了 io.Reader 的结构体类型,openReader 根据传入参数返回一个 MyReader 结构体指针。
你觉得这段程序会输出什么呢?会输出"bad reader"吗?
答案是刚好相反:
//example3 output valid reader
为了解释这个结果,我们再看两段简单的代码:
example4
var b interface{} = nil fmt.Println(b == nil)
example5
var a *Student = nil var b interface{} = a fmt.Println(b == nil)
输出分别为:
//example4 output true //example5 output false
相信仔细对比过后,你应该已经有答案了: 当一个指针赋值给 interface 类型时,无论此指针是否为 nil,赋值过的 interface 都不为 nil 。
ok,结论已经有了,那为什么是这样呢?还记得本文开头介绍的 interface 底层实现吗?无论是 iface 还是 eface,都有两个指针,指向数据的是通用指针,还有一个指针用于指定数据类型或方法集;当我们将一个 nil 指针赋值给 interface 时,实际是对 interface 的这两个指针分别赋值,虽言数据指针 data 为 nil,但是类型指针_type 或 tab 并不是 nil,他将指向你的空指针的类型,因此赋值的结果 interface 肯定不是 nil 啦!
什么?interface 还能嵌入 struct?
众所周知,一个新定义的 type 要想实现某个 interface,一定需要将该 interface 的所有方法都实现一遍。对吗?
老规矩,先上例子:
example6
type Talkable interface { TalkEnglish(string) TalkChinese(string) } type Student1 struct { Talkable Name string Age int } func main(){ a := Student1{Name: "aaa", Age: 12} var b Talkable = a fmt.Println(b) }
以上的代码时 100%能编译运行的。输出为:
// example6 output {<nil> aaa 12}
这是一种取巧的方法,将 interface 嵌入结构体,可以使该类型快速实现该 interface。所以,本小节开头的话并不成立。但是如果我们调一下方法呢?
example7
... func main(){ a := Student1{Name: "aaa", Age: 12} a.TalkEnglish("nice to meet you\n") }
可以预见到的,报错了:
//example7 output panic: runtime error: invalid memory address or nil pointer dereference
并没有实现 interface 的方法,当然会报错。我们可以只实现 interface 的一部分方法,比如我只需要用到 Talkable 的 TalkEnglish 方法:
func (s *Student1) TalkEnglish(s1 string) { fmt.Printf("I'm %s,%d years old,%s", s.Name, s.Age, s1) }
或者只需要讲中文:
func (s *Student1) TalkChinese(s1 string) { fmt.Printf("我是 %s, 今年%d岁,%s", s.Name, s.Age, s1) }
总而言之,嵌入 interface 的好处就是可以帮在整体类型兼容某个接口的前提下,允许你你针对你的应用场景只实现 interface 中的一部分方法。但是在使用时要注意没有实现的方法在调用时会 panic。
总结
interface 时 golang 编程中使用得非常频繁的特性,我们需要明白它的底层结构,以及一些编译和运行时的特殊之处,能帮我们避免一些不必要的麻烦:
-
interface 很类似
void*
,但在值类型的变量和 interface 类型变量相互赋值时,会发生数据的复制。 -
将某个类型的指针赋值给 interface,interface 的值永远不可能是 nil;
-
interface 可以嵌入结构体,帮类型快速实现接口,但是注意如果调用未实现的方法则会 panic;
12 月 15 日 Go语言中文网深圳Meetup,免费报名
报名方式点击底部 阅读原文
Recommend
-
117
AndroidStudio(3.x版本)的输入法之坑 Original...
-
75
-
79
一年一度的双十一马上就要来了,热衷于“买买买”的小伙伴们是否已经快要抑制不住体内的洪荒之力了呢?不过千万要记得:剁手需谨慎哟! 这两年的「双 11」,比以往来得都要早,去年阿里从 10 月 20 日开始造势,今年也不例外,想必这段时间以来,不少人已经各种求赞...
-
63
-
43
Prometheus 是一个开源监控系统,它本身已经成为了云原生中指标监控的事实标准,几乎所有 Kubernetes 的核心组件以及其它云原生系统都以 Prometheus 的指标格式输出自己的运行时监控信息。我在工作中也比较深入地使用过 Prometheus,最大...
-
61
导语:如果,将编程语言比作武功秘籍,C++无异于《九阴真经》。《九阴真经》威力强大、博大精深,经中所载内功、轻功、拳、掌、腿、刀法、剑法、杖法、...
-
36
12C ogg之坑爹又坑队友报错OGG-00868 ORA-01291: missing logf 同事正常操作,并停止一个ogg进程,数据库是12c的。ogg当然也是12c的版本。一切都是一个正常的操作,但是出了坑爹的效应,差不多四个人,搞了近3个小时吧。下面我...
-
61
.NET Core 3.0将会在 .NET Conf 大会上正式发布,截止今日发布了9个预览版,改动也是不少,由于没有持续关注,今天将前面开源的动态WebApi项目迁移到.NET...
-
39
通过上一篇对engine代码的分析,我们了解到 每展示一个platform view,flutter都会同时创建一个全屏的overlay view 。这个overlay view的作用,是解决platform view和flutter view遮挡的问题。 而这套实现机制,...
-
13
本文首发于泊浮目的简书:https://www.jia...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK