空結構體struct{}解析
http://www.golangtc.com/t/575442b8b09ecc02f7000057
本篇文章轉自David的”The empty struct”一文,原文地址連結是http://dave.cheney.net/2014/03/25/the-empty-struct 。歡迎大家訪問我的部落格,程式碼可以在<a href="https://github.com/Zuozuohao/GolangGOFPatterns" "="" target="_blank" style="box-sizing: border-box; background-color: transparent; color: rgb(65, 131, 196); text-decoration-line: none;">@Zuozuohao下載。
Introduction
這篇文章詳細介紹了我最喜歡的Go資料型別,空結構體—struct{}。
空結構體是沒有位段的結構體,以下是空結構體的一些例子:
type Q struct{}
var q struct{}
但是如果一個就結構體沒有位段,不包含任何資料,那麼他的用處是什麼?我們能夠利用空結構體完成什麼任務?
Width
在深入研究空結構體之前,我想先簡短的介紹一下關於結構體寬度的知識。
術語寬度來自於gc編譯器,但是他的詞源可以追溯到幾十年以前。
寬度描述了儲存一個數據型別例項需要佔用的位元組數,由於程序的記憶體空間是一維的,我更傾向於將寬度理解為Size(這個詞實在不知道怎麼翻譯了,請諒解)。
寬度是資料型別的一個屬性。Go程式中所有的例項都是一種資料型別,一個例項的寬度是由他的資料型別決定的,通常是8bit的整數倍。
我們可以通過unsafe.Sizeof()函式獲取任何例項的寬度:
var s string
var c complex128
fmt.Println(unsafe.Sizeof(s))// prints 8
fmt.Println(unsafe.Sizeof(c))// prints 16
陣列的寬度是他元素寬度的整數倍。
var a [3]uint32
fmt.Println(unsafe.Sizeof(a))// prints 12
結構體提供了定義組合型別的靈活方式,組合型別的寬度是欄位寬度的和,然後再加上填充寬度。
type S struct{
a uint16
b uint32
}
var s S
fmt.Println(unsafe.Sizeof(s))// prints 8, not 6
An empty struct
現在我們清楚的認識到空結構體的寬度是0,他佔用了0位元組的記憶體空間。
var s struct{}
fmt.Println(unsafe.Sizeof(s))// prints 0
由於空結構體佔用0位元組,那麼空結構體也不需要填充位元組。所以空結構體組成的組合資料型別也不會佔用記憶體空間。
type S struct{
A struct{}
B struct{}
}
var s S
fmt.Println(unsafe.Sizeof(s))// prints 0
What can you do with an empty struct
由於Go的正交性,空結構體可以像其他結構體一樣正常使用。正常結構體擁有的屬性,空結構體一樣具有。
你可以定義一個空結構體組成的陣列,當然這個切片不佔用記憶體空間。
var x [1000000000]struct{}
fmt.Println(unsafe.Sizeof(x))// prints 0
空結構體組成的切片的寬度只是他的頭部資料的長度,就像上例展示的那樣,切片元素不佔用記憶體空間。
var x = make([]struct{},1000000000)
fmt.Println(unsafe.Sizeof(x))// prints 12 in the playground
當然切片的內建子切片、長度和容量等屬性依舊可以工作。
var x = make([]struct{},100)
var y = x[:50]
fmt.Println(len(y), cap(y))// prints 50 100
你甚至可以定址一個空結構體,空結構體是可定址的,就像其他型別的例項一樣。
var a struct{}
var b =&a
有意思的是兩個空結構體的地址可以相等。
var a, b struct{}
fmt.Println(&a ==&b)// true
空結構體的元素也具有一樣的屬性。
a := make([]struct{},10)
b := make([]struct{},20)
fmt.Println(&a ==&b)// false, a and b are different slices
fmt.Println(&a[0]==&b[0])// true, their backing arrays are the same
為什麼會這樣?因為空結構體不包含位段,所以不儲存資料。如果空結構體不包含資料,那麼就沒有辦法說兩個空結構體的值不相等,所以空結構體的值就這樣相等了。
a :=struct{}{}// not the zero value, a real new struct{} instance
b :=struct{}{}
fmt.Println(a == b)// true
有興趣可以參考這篇文章” Two distinct zero-size variables may have the same address in memory”。
struct{} as a method receiver
現在讓我們展示一下空結構體如何像其他結構體工作,空結構體可以作為方法的接收者。
type S struct{}
func (s *S) addr(){ fmt.Printf("%p\n", s)}
func main(){
var a, b S
a.addr()// 0x1beeb0
b.addr()// 0x1beeb0
}
在這篇文章中空結構體的地址是0x1beeb0,但是這個值可能隨著Go版本的不同而發生變化。
Wrapping up
非常感謝您讀完這篇冗長的文章,但是我還有一些其他的內容需要說明,請見隨後更新。