29|接口:为什么nil接口不等于nil?
该思维导图由 AI 生成,仅供参考
接口的静态特性与动态特性
- 深入了解
- 翻译
- 解释
- 总结
Go语言接口类型变量的静态和动态特性以及运行时的内部表示是本文的重点内容。通过解释eface和iface的结构,以及它们在运行时的表示,清晰展现了不同接口类型变量在运行时的内部结构。文章还介绍了一个经典困惑:nil的error值不等于nil的情况,并通过示例代码解释了其原因。此外,文章还提供了一个在Go 1.17版本上测试通过的方法,用于输出接口类型变量的内部表示的详细信息。读者可以通过本文深入了解接口类型的运行时表示层面,对于想要深入了解Go语言接口特性的读者具有很高的参考价值。文章还介绍了接口类型的装箱原理,通过汇编代码展示了装箱操作的实现逻辑,并解释了编译器如何选择适当的convT2X函数参与装箱操作。最后,文章提出了性能优化的建议,强调了避免或减少装箱操作对性能敏感系统的重要性。整体而言,本文通过深入的技术讲解和示例代码,帮助读者更好地理解Go语言接口类型变量的特性和运行时表示,同时提供了性能优化的建议,具有很高的实用价值。
《Tony Bai · Go 语言第一课》,新⼈⾸单¥59
全部留言(26)
- 最新
- 精选
- Calvin思考题有2 种方法: 1)returnsError() 函数不返回 error 非空接口类型,而是直接返回结构体指针 *MyError(明确的类型,阻止自动装箱); 2)不要直接 err != nil 这样判断,而是使用类型断言来判断: if e, ok := err.(*MyError); ok && e != nil { fmt.Printf("error occur: %+v\n", e) return } PS:Go 的“接口”在编程中需要特别注意,必须搞清楚接口类型变量在运行时的表示,以避免踩坑!!!
作者回复: 👍
2022-01-0518 - return老师讲的太好, 这一篇 知识密度相当大啊, 就这一篇就值专栏的价格了。 感谢老师如此用心的输出。
作者回复: 受宠若惊😁
2021-12-2917 - Geralt修改方法: 1. 把returnsError()里面p的类型改为error 2. 删除p,直接return &ErrBad或者nil
作者回复: ✅
2021-12-29316 - Slowdive老师, 请问这里发生装箱了吗? 返回类型是error, 是一个接口, p是*MyError, p的方法列表覆盖了error这个接口, 所以是可以赋值给error类型的变量。 这个过程发生了隐式转换,赋值给接口类型,做装箱创建iface, p != nil就成了 (&tab, 0x0) != (0x0, 0x0) func returnsError() error { var p *MyError = nil if bad() { p = &ErrBad } return p } 这样理解对吗?
作者回复: 正确。
2022-04-2010 - aoe原来装箱是这样:将任意类型赋值给一个接口类型变量就是装箱操作。 接口类型的装箱实际就是创建一个 eface 或 iface 的过程
作者回复: 👍
2022-01-0329 - Geek_a6104eeif: (0x10b38c0,0x10e9b30) err: (0x10eb690,0x10e9b30) eif = err: true eface: {_type:0x10b38c0 data:0x10e9b30} _type: {size:8 ptrdata:0 hash:1156555957 tflag:15 align:8 fieldAlign:8 kind:2 equal:0x10032e0 gcdata:0x10e9a60 str:4946 ptrToThis:58496} data: bad error iface: {tab:0x10eb690 data:0x10e9b30} itab: {inter:0x10b5e20 _type:0x10b38c0 hash:1156555957 _:[0 0 0 0] fun:[17454976]} inter: {typ:{size:16 ptrdata:16 hash:235953867 tflag:7 align:8 fieldAlign:8 kind:20 equal:0x10034c0 gcdata:0x10d2418 str:3666 ptrToThis:26848} pkgpath:{bytes:<nil>} mhdr:[{name:2592 ityp:43520}]} _type: {size:8 ptrdata:0 hash:1156555957 tflag:15 align:8 fieldAlign:8 kind:2 equal:0x10032e0 gcdata:0x10e9a60 str:4946 ptrToThis:58496} fun: [0x10a5780(17454976),] data: bad error 请问为什么data会是bad error不应该是5吗
作者回复: 好问题。 为什么输出bad error而不是5,是因为我们的dumpT函数的实现: func dumpT(dataOfIface unsafe.Pointer) { var p *T = (*T)(dataOfIface) fmt.Printf("\t data: %+v\n", *p) } 这里的Printf使用了%+v。 在标准库fmt包的manual(https://pkg.go.dev/fmt)中有,当verb为%v时,如果操作数实现了error接口,那么Printf将会调用这个操作数的Error方法将其转换为字符串。 原文:If an operand implements the error interface, the Error method will be invoked to convert the object to a string 所以这里输出的是bad error。 可以再举一个简单的例子: package main import "fmt" type T int func (t T) Error() string { return "bad error" } func main() { var t = T(5) fmt.Printf("%d\n", t) // 5 fmt.Printf("%v\n", t) // bad error }
2022-07-046 - CalvinGo 指针这块,感觉可以单独抽出一讲来讲下,并且结合unsafe 讲解,不知道大白老师能否满足大家的愿望呢?😂
作者回复: 好多人提出来了,后续定弄个加餐说说指针。不过需要把所有正文都更完后,编辑老师催的紧,你了解的:)
2022-01-056 - 郑泽洲请教老师,接口类型装箱过程为什么普遍要把原来的值复制一份到data?(除了staticuint64s等特例)直接用原来的值不行吗,还能提升点性能
作者回复: 好问题! 假设按照你说的,interface中直接用原先的值,那么interface类型在runtime中的表示一定是(type, ptr)的二元组。而ptr指向原值的地址。这样的情况下,看个例子: func foo(i interface{}) { i.(int) = 8 } var a int = 6 var i interface{} = a i.(int) = 7 println(a) // a = 7 这似乎还说得过去。 但是如果将i传递给函数foo: foo(i) foo对i的修改将都反映到a上: println(a) // a = 8 这与值拷贝语义似乎有悖。
2022-02-2635 - 在下宝龙、老师您好,在 eif2 = 17 这个操作后,输出后的data ,0xc00007ef48 和0x10eb3d0 不相等呀,为甚么说他们是一样的 eif1: (0x10ac580,0xc00007ef48) eif2: (0x10ac580,0x10eb3d0)
作者回复: 判相等不要看data指针的值,要看data指针指向的内存块中存储的值是否相同。
2021-12-2935 - lesserror大白老师的这一节干货很多,读的意犹未尽。有几个疑惑点,麻烦老师解忧。 1. 文中类似:“_type” 这种命名,前面加下划线,这种有什么含义呢? 2. 文中关于打印两类接口内部详细信息的代码中,运用了大量的 * 还有 & 再加上 unsafe.Pointer 的使用,看起来会非常困惑,希望老师后面能讲一讲Go的指针吧。刚从动态语言转过来,确实应该好好理解一下。不然后面写出来的代码一定会有很多潜在的风险。
作者回复: 1. 没有啥特殊含义。我们自己写代码,不要用以下划线为前缀的命名方式。 2. 指针加餐后续应该会加上。
2021-12-3123