有一天,我用谷歌搜索一个 Go 问题,谷歌将我引导到 Go FAQ 页面。问题解决后,我阅读了整个 FAQ。
这是一次很棒的阅读,我从文章中学到了很多。但我注意到一个问题, 为什么数组是值,而 map、slice 和 channel 是引用?答复如下:
此话题历史久远。在早期,map 和 channel 都是语法指针,不能声明和使用非指针实例。此外,我们在竭尽全力探索数组如何工作。最终,我们认为指针和值的严格分离使语言更难使用。将这些类型更改为对关联的共享数据结构的引用,就解决了这些问题。改变给语言增加了一些令人遗憾的复杂性,但却对可用性产生了很大的影响:Go 一经推出,就成为了一种更高效、更舒服的语言。
令我惊讶的是,Go 官方文档仍在使用“引用类型”的概念,因为自 2013 年 4 月 3 日以来,“引用类型”的概念已从 Go 规范中完全删除。现在 Go 规范中有 10 个“引用”词,没有一个代表“引用类型”的概念。
另一个惊喜是这句话:
…指针和值的严格分离使该语言更难使用。…
此答复将指针和值视为两个不兼容的概念。但是,Go 规范将指针视为特殊值,指针被称为“指针值”。值只是类型的实例。显然,Go 规范中“指针”一词的定义很好。我认为如果使用“指针值和非指针值”会更好。
所以,我认为此答复给 Go 社区带来了很多困惑。它与当前 Go 规范冲突,并且打破了概念的一致性。
谈回第一个惊喜,我认为称呼 map/slice/channel 值为引用值完全没有必要。不仅因为 “reference” 这个词在编程世界中被滥用了,还因为 map/slice/channel 值只是普通的正常值
以下是 map/slice/channel 类型的内部声明:
Type Family | Type Declaration |
map | struct { m *internalHashtable } |
channel | struct { c *internalChannel } |
slice | struct { array *internalArray len int cap int } |
请注意,上面的声明可能不完全与官方或非官方的 Go 实现中的声明相同。Go 实现可以直接使用指针表示 map 和 channel 的值,但 Go 规范/编译器永远不会将它们视为指针。因此,你可以放心的将 map/slice/channel 类型视为上面声明的指针包装类型,而不会有任何问题。
从上面的声明,很容易得出结论:map/slice/channel 只是包含一个非导出指针字段的结构类型。将它们称为引用类型是完全没有必要的。
Map 和 slice 类型与一般结构类型确实有一个区别。与一般结构类型不同,对于 map 或 slice 类型 T,T{} 不是 T 的零值。但这不是将 map 或 slice 类型拆分为新的引用类型类别的好理由。
通过理解 Go 的以下两个规则:
- map/slice/channel 值只是普通的指针包装结构的值
- 所有赋值,包括参数传递等,都是浅值复制(指针指向的值不会被复制)
Gopher 应该清楚地理解赋值中的 dest 和 source map/slice/channel 值将共享被包装的指针所指向的同一底层数据。
概念是用来帮助程序员理解语言的机制,而不是混淆他们。值、指针值和非指针值的概念足以让 Gopher 理解 Go。
我希望 Go 文档不会破坏概念定义的一致性。
原文:https://www.tapirgames.com/blog/golang-has-no-reference-values
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/08-23-2021/there-are-no-reference-types-in-go-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!