本篇文章轉(zhuǎn)自David的"The empty struct"一文,原文地址鏈接是http://dave.cheney.net/2014/03/25/the-empty-struct。
Introduction
這篇文章詳細(xì)介紹了我最喜歡的Go數(shù)據(jù)類型,空結(jié)構(gòu)體--struct{}。
空結(jié)構(gòu)體是沒有位段的結(jié)構(gòu)體,以下是空結(jié)構(gòu)體的一些例子:
type Q struct{}
var q struct{}
但是如果一個(gè)就結(jié)構(gòu)體沒有位段,不包含任何數(shù)據(jù),那么他的用處是什么?我們能夠利用空結(jié)構(gòu)體完成什么任務(wù)?
Width
在深入研究空結(jié)構(gòu)體之前,我想先簡短的介紹一下關(guān)于結(jié)構(gòu)體寬度的知識。
術(shù)語寬度來自于gc編譯器,但是他的詞源可以追溯到幾十年以前。
寬度描述了存儲一個(gè)數(shù)據(jù)類型實(shí)例需要占用的字節(jié)數(shù),由于進(jìn)程的內(nèi)存空間是一維的,我更傾向于將寬度理解為Size(這個(gè)詞實(shí)在不知道怎么翻譯了,請諒解)。
寬度是數(shù)據(jù)類型的一個(gè)屬性。Go程序中所有的實(shí)例都是一種數(shù)據(jù)類型,一個(gè)實(shí)例的寬度是由他的數(shù)據(jù)類型決定的,通常是8bit的整數(shù)倍。
我們可以通過unsafe.Sizeof()函數(shù)獲取任何實(shí)例的寬度:
var s string
var c complex128
fmt.Println(unsafe.Sizeof(s)) // prints 8
fmt.Println(unsafe.Sizeof(c)) // prints 16
http://play.golang.org/p/4mzdOKW6uQ
數(shù)組的寬度是他元素寬度的整數(shù)倍。
var a [3]uint32
fmt.Println(unsafe.Sizeof(a)) // prints 12
http://play.golang.org/p/YC97xsGG73
結(jié)構(gòu)體提供了定義組合類型的靈活方式,組合類型的寬度是字段寬度的和,然后再加上填充寬度。
type S struct {
a uint16
b uint32
}
var s S
fmt.Println(unsafe.Sizeof(s)) // prints 8, not 6
An empty struct
現(xiàn)在我們清楚的認(rèn)識到空結(jié)構(gòu)體的寬度是0,他占用了0字節(jié)的內(nèi)存空間。
var s struct{}
fmt.Println(unsafe.Sizeof(s)) // prints 0
由于空結(jié)構(gòu)體占用0字節(jié),那么空結(jié)構(gòu)體也不需要填充字節(jié)。所以空結(jié)構(gòu)體組成的組合數(shù)據(jù)類型也不會占用內(nèi)存空間。
type S struct {
A struct{}
B struct{}
}
var s S
fmt.Println(unsafe.Sizeof(s)) // prints 0
http://play.golang.org/p/PyGYFmPmMt
What can you do with an empty struct
由于Go的正交性,空結(jié)構(gòu)體可以像其他結(jié)構(gòu)體一樣正常使用。正常結(jié)構(gòu)體擁有的屬性,空結(jié)構(gòu)體一樣具有。
你可以定義一個(gè)空結(jié)構(gòu)體組成的數(shù)組,當(dāng)然這個(gè)切片不占用內(nèi)存空間。
var x [1000000000]struct{}
fmt.Println(unsafe.Sizeof(x)) // prints 0
http://play.golang.org/p/0lWjhSQmkc
空結(jié)構(gòu)體組成的切片的寬度只是他的頭部數(shù)據(jù)的長度,就像上例展示的那樣,切片元素不占用內(nèi)存空間。
var x = make([]struct{}, 1000000000)
fmt.Println(unsafe.Sizeof(x)) // prints 12 in the playground
http://play.golang.org/p/vBKP8VQpd8
當(dāng)然切片的內(nèi)置子切片、長度和容量等屬性依舊可以工作。
var x = make([]struct{}, 100)
var y = x[:50]
fmt.Println(len(y), cap(y)) // prints 50 100
http://play.golang.org/p/8cO4SbrWVP
你甚至可以尋址一個(gè)空結(jié)構(gòu)體,空結(jié)構(gòu)體是可尋址的,就像其他類型的實(shí)例一樣。
var a struct{}
var b = &a
有意思的是兩個(gè)空結(jié)構(gòu)體的地址可以相等。
var a, b struct{}
fmt.Println(&a == &b) // true
http://play.golang.org/p/uMjQpOOkX1
空結(jié)構(gòu)體的元素也具有一樣的屬性。
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
http://play.golang.org/p/oehdExdd96
為什么會這樣?因?yàn)榭战Y(jié)構(gòu)體不包含位段,所以不存儲數(shù)據(jù)。如果空結(jié)構(gòu)體不包含數(shù)據(jù),那么就沒有辦法說兩個(gè)空結(jié)構(gòu)體的值不相等,所以空結(jié)構(gòu)體的值就這樣相等了。
a := struct{}{} // not the zero value, a real new struct{} instance
b := 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
現(xiàn)在讓我們展示一下空結(jié)構(gòu)體如何像其他結(jié)構(gòu)體工作,空結(jié)構(gòu)體可以作為方法的接收者。
type S struct{}
func (s *S) addr() { fmt.Printf("%p\n", s) }
func main() {
var a, b S
a.addr() // 0x1beeb0
b.addr() // 0x1beeb0
}
http://play.golang.org/p/YSQCczP-Pt
在這篇文章中空結(jié)構(gòu)體的地址是0x1beeb0,但是這個(gè)值可能隨著Go版本的不同而發(fā)生變化。
Wrapping up
非常感謝您讀完這篇冗長的文章,但是我還有一些其他的內(nèi)容需要說明,請見隨后更新。