有一天,我用谷歌搜尋一個 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 文件不會破壞概念定義的一致性。
本文作者:cyningsun
本文地址: https://www.cyningsun.com/08-...
版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-ND 3.0 CN 許可協議。轉載請註明出處!