1. 程式人生 > >空結構體struct{}解析

空結構體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{}。

空結構體是沒有位段的結構體,以下是空結構體的一些例子:

  1. type Q struct{}
  2. var q struct{}

但是如果一個就結構體沒有位段,不包含任何資料,那麼他的用處是什麼?我們能夠利用空結構體完成什麼任務?

Width

在深入研究空結構體之前,我想先簡短的介紹一下關於結構體寬度的知識。

術語寬度來自於gc編譯器,但是他的詞源可以追溯到幾十年以前。

寬度描述了儲存一個數據型別例項需要佔用的位元組數,由於程序的記憶體空間是一維的,我更傾向於將寬度理解為Size(這個詞實在不知道怎麼翻譯了,請諒解)。

寬度是資料型別的一個屬性。Go程式中所有的例項都是一種資料型別,一個例項的寬度是由他的資料型別決定的,通常是8bit的整數倍。

我們可以通過unsafe.Sizeof()函式獲取任何例項的寬度:

  1. var s string
  2. var c complex128
  3. fmt.Println(unsafe.Sizeof(s))// prints 8
  4. fmt.Println(unsafe.Sizeof(c))// prints 16

陣列的寬度是他元素寬度的整數倍。

  1. var a [3]uint32
  2. fmt.Println(unsafe.Sizeof(a))// prints 12

結構體提供了定義組合型別的靈活方式,組合型別的寬度是欄位寬度的和,然後再加上填充寬度。

  1. type S struct{
  2. a uint16
  3. b uint32
  4. }
  5. var s S
  6. fmt.Println(unsafe.Sizeof(s))// prints 8, not 6

An empty struct

現在我們清楚的認識到空結構體的寬度是0,他佔用了0位元組的記憶體空間。

  1. var s struct{}
  2. fmt.Println(unsafe.Sizeof(s))// prints 0

由於空結構體佔用0位元組,那麼空結構體也不需要填充位元組。所以空結構體組成的組合資料型別也不會佔用記憶體空間。

  1. type S struct{
  2. A struct{}
  3. B struct{}
  4. }
  5. var s S
  6. fmt.Println(unsafe.Sizeof(s))// prints 0

What can you do with an empty struct

由於Go的正交性,空結構體可以像其他結構體一樣正常使用。正常結構體擁有的屬性,空結構體一樣具有。

你可以定義一個空結構體組成的陣列,當然這個切片不佔用記憶體空間。

  1. var x [1000000000]struct{}
  2. fmt.Println(unsafe.Sizeof(x))// prints 0

空結構體組成的切片的寬度只是他的頭部資料的長度,就像上例展示的那樣,切片元素不佔用記憶體空間。

  1. var x = make([]struct{},1000000000)
  2. fmt.Println(unsafe.Sizeof(x))// prints 12 in the playground

當然切片的內建子切片、長度和容量等屬性依舊可以工作。

  1. var x = make([]struct{},100)
  2. var y = x[:50]
  3. fmt.Println(len(y), cap(y))// prints 50 100

你甚至可以定址一個空結構體,空結構體是可定址的,就像其他型別的例項一樣。

  1. var a struct{}
  2. var b =&a

有意思的是兩個空結構體的地址可以相等。

  1. var a, b struct{}
  2. fmt.Println(&a ==&b)// true

空結構體的元素也具有一樣的屬性。

  1. a := make([]struct{},10)
  2. b := make([]struct{},20)
  3. fmt.Println(&a ==&b)// false, a and b are different slices
  4. fmt.Println(&a[0]==&b[0])// true, their backing arrays are the same

為什麼會這樣?因為空結構體不包含位段,所以不儲存資料。如果空結構體不包含資料,那麼就沒有辦法說兩個空結構體的值不相等,所以空結構體的值就這樣相等了。

  1. a :=struct{}{}// not the zero value, a real new struct{} instance
  2. b :=struct{}{}
  3. fmt.Println(a == b)// true

有興趣可以參考這篇文章” Two distinct zero-size variables may have the same address in memory”。

struct{} as a method receiver

現在讓我們展示一下空結構體如何像其他結構體工作,空結構體可以作為方法的接收者。

  1. type S struct{}
  2. func (s *S) addr(){ fmt.Printf("%p\n", s)}
  3. func main(){
  4. var a, b S
  5. a.addr()// 0x1beeb0
  6. b.addr()// 0x1beeb0
  7. }

在這篇文章中空結構體的地址是0x1beeb0,但是這個值可能隨著Go版本的不同而發生變化。

Wrapping up

非常感謝您讀完這篇冗長的文章,但是我還有一些其他的內容需要說明,請見隨後更新。