[譯]空結構體

itfanr發表於2016-10-16

這篇文章探討了我喜歡的Go資料型別,空結構體。

空結構體是一個沒有field的結構體型別。這裡有幾個例子,有命名和匿名形式:

type Q struct{}
var q struct{}

所以,如果空結構體沒有成員,我們該怎麼使用它?

width

在深入研究空結構體本身前,我想簡要討論下width

術語width來自於gc編譯器,儘管它的詞源可能追溯到幾十年年。

width描述了型別例項佔用的位元組數目。因為一個程式的地址空間是一維的,我認為witdh比size更合適。

width一個型別的屬性。因為Go程式的每個值都有一個型別,值型別定義了它的witdh,一般是8位元的倍數。

我們可以發現任何值的寬度,它的型別的width使用unsafe.Sizeof()函式:

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

陣列型別的width是它的元素型別的倍數:

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

http://play.golang.org/p/YC97xsGG73

結構體提供了更靈活的方式來定義組合型別,它的width是所有組成型別的width的總和,加上padding:

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

上面的例子演示了padding的一方面,值必須在記憶體中對齊為它的width的倍數。在這個場景中,在a和b中間被編譯器加入了2個位元組的padding。

更新:Russ Cox已經解釋了width和對齊無關。你可以閱讀下面的評論

空型別

現在,我們已經探討了width,很明顯空型別的width是零。它佔用了零位元組的儲存空間:

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

因為空型別佔用了零位元組,所以它不需要填充。這樣,空結構體組成的一個結構體也不佔用儲存空間:

type S struct {
        A struct{}
        B struct{}
}
var s S
fmt.Println(unsafe.Sizeof(s)) // prints 0

http://play.golang.org/p/PyGYFmPmMt

我們可以用空型別做什麼

適用於Go語言的正交性,空型別和其他型別一樣,是一個結構型別。你所使用的正常的結構體的所有的屬性適用於空的結構。

你可以宣告一個結構體陣列struct{}s,但是他們當然不會佔用儲存空間:

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

http://play.golang.org/p/0lWjhSQmkc

struct{}s的切片僅僅消耗他們的slice頭的空間。就像上面演示的那樣,他們的後端陣列不消耗空間:

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

http://play.golang.org/p/vBKP8VQpd8

當然,正常的子切片,內建的len和cap和預期一樣工作:

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

http://play.golang.org/p/8cO4SbrWVP

你可以獲取struct{}值的地址,當它的可以地址化,就像其他值一樣:

var a struct{}
var b = &a

有意思的是,兩個struct{}值的地址可能是相同的:

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

http://play.golang.org/p/uMjQpOOkX1

對於[]struct{}s,這個屬性也是可見的:

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

為什麼是這樣?如果你考慮一下,空結構體不包含成員,所以可以不包含資料。如果空結構體不包含資料,不能決定是否兩個struct{}值是否是相同的。它們在效果上,是可替代的。

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

http://play.golang.org/p/K9qjnPiwM8

注意:這個屬性不是spec所需要的,但是注意:Two distinct zero-size variables may have the same address in memory.

struct{} 作為 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
}

http://play.golang.org/p/YSQCczP-Pt

在這個例子中,展示了all zero sized值的地址為0x1beeb0。精確的地址可能因Go的版本而不同。

封裝

謝謝你的閱讀。本文已接近800字,比預期更多,我還有更多的計劃。

儘管本文關注於語言黑盒,有一個空結構體重要的實際用途。chan struct{}用來在不同的go routine之間傳送訊號。

翻譯

本文的中文版在這裡

更新:Damian Gryski指出我忽略了 Brad Fitzpatrick的iter包。我留下它作為讀者的練習來探索Brad的深遠影響的貢獻。

相關文章

  1. Struct composition with Go
  2. Friday pop quiz: the smallest buffer
  3. Constant errors
  4. Stupid Go declaration tricks

英文原文

如果我翻譯得不對,請幫我改善: https://github.com/itfanr/articles-about-golang/blob/master/2016-10/2.the-empty-struct.md

相關文章