空結構體struct{}解析
本篇文章轉自David的"The empty struct"一文,原文地址連結是http://dave.cheney.net/2014/03/25/the-empty-struct。
Introduction
這篇文章詳細介紹了我最喜歡的Go資料型別,空結構體--struct{}。
空結構體是沒有位段的結構體,以下是空結構體的一些例子:
type Q struct{}var q struct{}
但是如果一個就結構體沒有位段,不包含任何資料,那麼他的用處是什麼?我們能夠利用空結構體完成什麼任務?
Width
在深入研究空結構體之前,我想先簡短的介紹一下關於結構體寬度的知識。
術語寬度來自於gc編譯器,但是他的詞源可以追溯到幾十年以前。
寬度描述了儲存一個數據型別例項需要佔用的位元組數,由於程序的記憶體空間是一維的,我更傾向於將寬度理解為Size(這個詞實在不知道怎麼翻譯了,請諒解)。
寬度是資料型別的一個屬性。Go程式中所有的例項都是一種資料型別,一個例項的寬度是由他的資料型別決定的,通常是8bit的整數倍。
我們可以通過unsafe.Sizeof()函式獲取任何例項的寬度:
var s stringvar c complex128
fmt.Println(unsafe.Sizeof(s)) // prints 8fmt.Println(unsafe.Sizeof(c)) // prints 16
http://play.golang.org/p/4mzdOKW6uQ
陣列的寬度是他元素寬度的整數倍。
var a [3]uint32
fmt.Println(unsafe.Sizeof(a)) // prints 12
http://play.golang.org/p/YC97xsGG73
結構體提供了定義組合型別的靈活方式,組合型別的寬度是欄位寬度的和,然後再加上填充寬度。
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 Sfmt.Println(unsafe.Sizeof(s)) // prints 0
http://play.golang.org/p/PyGYFmPmMt
What can you do with an empty struct
由於Go的正交性,空結構體可以像其他結構體一樣正常使用。正常結構體擁有的屬性,空結構體一樣具有。
你可以定義一個空結構體組成的陣列,當然這個切片不佔用記憶體空間。
var x [1000000000]struct{}
fmt.Println(unsafe.Sizeof(x)) // prints 0
http://play.golang.org/p/0lWjhSQmkc
空結構體組成的切片的寬度只是他的頭部資料的長度,就像上例展示的那樣,切片元素不佔用記憶體空間。
var x = make([]struct{}, 1000000000)
fmt.Println(unsafe.Sizeof(x)) // prints 12 in the playground
http://play.golang.org/p/vBKP8VQpd8
當然切片的內建子切片、長度和容量等屬性依舊可以工作。
var x = make([]struct{}, 100)var y = x[:50]
fmt.Println(len(y), cap(y)) // prints 50 100
http://play.golang.org/p/8cO4SbrWVP
你甚至可以定址一個空結構體,空結構體是可定址的,就像其他型別的例項一樣。
var a struct{}var b = &a
有意思的是兩個空結構體的地址可以相等。
var a, b struct{}
fmt.Println(&a == &b) // true
http://play.golang.org/p/uMjQpOOkX1
空結構體的元素也具有一樣的屬性。
a := make([]struct{}, 10)
b := make([]struct{}, 20)
fmt.Println(&a == &b) // false, a and b are different slicesfmt.Println(&a[0] == &b[0]) // true, their backing arrays are the same
http://play.golang.org/p/oehdExdd96
為什麼會這樣?因為空結構體不包含位段,所以不儲存資料。如果空結構體不包含資料,那麼就沒有辦法說兩個空結構體的值不相等,所以空結構體的值就這樣相等了。
a := struct{}{} // not the zero value, a real new struct{} instanceb := struct{}{}
fmt.Println(a == b) // true
http://play.golang.org/p/K9qjnPiwM8
有興趣可以參考這篇文章" 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("%pn", s) }func main() { var a, b S
a.addr() // 0x1beeb0
b.addr() // 0x1beeb0}
http://play.golang.org/p/YSQCczP-Pt
在這篇文章中空結構體的地址是0x1beeb0,但是這個值可能隨著Go版本的不同而發生變化。