翻译 || 总结 - Go语言中的空结构体(The empty struct)

翻译 || 总结 – Go语言中的空结构体(The empty struct)

总结

在Go语言中,结构体是一种用户自定义的数据类型。通常情况下,结构体中包含一个或多个字段,每个字段都有一个对应的数据类型。但是,Go语言也支持定义一个不包含任何字段的结构体,也就是所谓的空结构体。

为什么要定义一个空结构体呢?因为在Go语言中,变量的类型也是其内存占用的大小。如果一个变量不需要存储任何数据,那么它所占用的内存大小就可以为0。而定义一个空结构体正好可以满足这个需求。例如,可以将一个空结构体作为某个通道的元素类型,这样就可以实现一个类似信号量的功能。

空结构体的定义非常简单,只需要使用 struct{} 即可。需要注意的是,空结构体虽然不包含任何字段,但是它仍然是一种类型,因此可以定义变量、类型别名、方法等。

这篇文章还讨论了空结构体在Go语言中的一些应用场景。其中一个非常常见的应用场景是用空结构体作为某些数据类型的占位符。比如,当我们需要存储一些与数据本身无关的元数据时,可以使用空结构体来表示这个元数据。

另外一个应用场景是使用空结构体作为函数的参数或返回值。这种用法可以让函数返回一个没有实际意义的值,但是可以方便地判断函数的执行结果。例如,标准库中的 sync 包中的 WaitGroup 类型就是通过空结构体作为参数来实现的。

最后,作者还讨论了一些关于空结构体的性能优化的问题。因为空结构体的大小为0,所以在某些情况下,使用空结构体可以提高程序的运行效率。但是,在其他情况下,空结构体可能会带来一些不必要的性能开销。因此,在使用空结构体时,需要根据具体的应用场景进行权衡和选择。

最后,空结构体虽然看起来非常简单,但是在Go语言中具有非常重要的应用价值。通过使用空结构体,我们可以实现一些特殊的功能,并且在一定程度上提高程序的运行效率。

另外,空结构体还有一个有趣的应用,即用于实现类似 Set 的数据结构。在 Go 语言中,map 可以用于实现 Set,但是如果只是想存储元素的存在与否,使用 map 可能会带来一定的性能开销。而使用空结构体作为 Set 中的元素类型,可以避免这个问题。因为空结构体不占用内存空间,所以存储元素的开销很小,同时也能保证元素的唯一性。例如:

type Set map[string]struct{}

func (s Set) Add(str string) {
    s[str] = struct{}{}
}

func (s Set) Contains(str string) bool {
    _, ok := s[str]
    return ok
}

在上面的例子中,Set 类型实际上是一个 map,但是它的 value 类型是 struct{},也就是空结构体。在 Add 方法中,我们直接将空结构体作为 map 的 value,而在 Contains 方法中,我们只需要判断 map 是否存在指定的 key 即可。这样就实现了一个高效、轻量级的 Set 数据结构。

除了上面介绍的应用场景之外,空结构体还可以用于实现某些特殊的数据结构,例如链表、树等。此外,空结构体还可以用于实现某些并发模式,例如信号量、锁等。总之,空结构体虽然看起来非常简单,但是在 Go 语言中具有非常重要的应用价值。通过使用空结构体,我们可以实现一些特殊的功能,并且在一定程度上提高程序的效率和性能。

需要注意的是,使用空结构体也存在一些需要注意的地方。首先,如果一个变量的类型是空结构体,那么它的值永远都是相同的,因为空结构体不包含任何字段。因此,如果使用空结构体定义一个全局变量,那么所有使用该变量的代码都会共享同一个实例。如果在并发环境中使用空结构体,需要特别小心,以避免出现数据竞争等问题。

另外,空结构体并不是 Go 语言中唯一的占用 0 字节内存的类型。例如,[0]byte 也是一种占用 0 字节内存的类型。不过,相比于 [0]byte,空结构体更加灵活和易用,因此在实际开发中使用更为广泛。

在 Go 语言中,空结构体是一个非常特殊的类型。虽然它看起来非常简单,但是在实际开发中却有着非常重要的应用价值。通过使用空结构体,我们可以实现一些特殊的功能,提高程序的效率和性能。当然,在使用空结构体时需要特别小心,以避免出现不必要的问题。

博客原文

The empty struct | Dave Cheney https://dave.cheney.net/2014/03/25/the-empty-struct,原文结尾有中文版链接,但是打不开,就自己翻译一遍,水平不高,见谅

Introduction

This post explores the properties of my favourite Go data type, the empty struct.

The empty struct is a struct type that has no fields. Here are a few examples in named and anonymous forms

type Q struct{}
var q struct{}

So, if an empty struct contains no fields, contains no data, what use is it ? What can we do with it ?

Width

Before diving into the empty struct itself, I wanted to take a brief detour to talk about width.

The term width comes, like most terms, from the gc compiler, although its etymology probably goes back decades.

Width describes the number of bytes of storage an instance of a type occupies. As a process’s address space is one dimensional, I think width is a more apt than size.

Width is a property of a type. As every value in a Go program has a type, the width of the value is defined by its type and is always a multiple of 8 bits.

We can discover the width of any value, and thus the width of its type using the unsafe.Sizeof() function.

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

The width of an array type is a multiple of its element type.

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

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

Structs provide a more flexible way of defining composite types, whose width is the sum of the width of the constituent types, plus padding

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

The example above demonstrates one aspect of padding, that a value must be aligned in memory to a multiple of its width. In this case there are two bytes of padding added by the compiler between a and b.

Update Russ Cox has kindly written to explain that width is unrelated to alignment. You can read his comment below.

Now that we’ve explored width it should be evident that the empty struct has a width of zero. It occupies zero bytes of storage.

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

As the empty struct consumes zero bytes, it follows that it needs no padding. Thus a struct comprised of empty structs also consumes no storage.

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

True to Go’s orthogonality, an empty struct is a struct type like any other. All the properties you are used to with normal structs apply equally to the empty struct.

You can declare an array of structs{}s, but they of course consume no storage.

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

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

Slices of struct{}s consume only the space for their slice header. As demonstrated above, their backing array consumes no space.

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

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

Of course the normal subslice, len, and cap builtins work as expected.

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

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

You can take the address of a struct{} value, when it is addressable, just like any other value.

var a struct{}
var b = &a

Interestingly, the address of two struct{} values may be the same.

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

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

This property is also observable for []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

Why is this? Well if you think about it, empty structs contain no fields, so can hold no data. If empty structs hold no data, it is not possible to determine if two struct{} values are different. They are in effect, fungible.

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

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

note: this property is not required by the spec, but it does note that Two distinct zero-size variables may have the same address in memory.

struct{} as a method receiver

Now we’ve demonstrated that empty structs behave just like any other type, it follows that we may use them as method receivers.

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

In this example it shows that the address of all zero sized values is 0x1beeb0. The exact address will probably vary for different versions of Go.

Wrapping up

Thank you for reading this far. At close to 800 words this article turned out to be longer than expected, and there was still more I was planning to write.

While this article concentrated on language obscura, there is one important practical use of empty structs, and that is the chan struct{} construct used for signaling between go routines

I’ve talked about the use of chan struct{} in my Curious Channels article.

Translations

Update Damian Gryski pointed out that I had omitted Brad Fitzpatrick’s [iter](http://godoc.org/github.com/bradfitz/iter) package. I’ll leave it as an exercise to the reader to explore the profound implications of Brad’s contribution.

翻译

介绍

这篇博客将探索我最喜欢的Go语言的数据结构——空struct

空struct就是没有字段的结构,下面是命名和匿名形式的表示:

type Q struct{}
var q struct{}

那么,如果一个空结构不包含字段,不包含数据,它有什么用呢?我们能用它做什么?

宽度(width该怎么翻译?)

在深入研究空结构本身之前,我想绕道而行,谈谈宽度。

与大多数术语一样,术语宽度来自 gc 编译器,尽管它的词源可能可以追溯到几十年前。

宽度描述类型实例占用的存储字节数。由于进程的地址空间是一维的,我认为宽度比大小更合适。

宽度是类型的属性。由于 Go 程序中的每个值都有一个类型,值的宽度由其类型定义,并且始终是 8 位的倍数。https://en.wikipedia.org/wiki/8-bit_computing

我们可以使用 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

数组类型的宽度是其元素类型的倍数。

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

上面的示例演示了填充的一个方面,即一个值必须在内存中对齐为其宽度的倍数。在这种情况下,编译器在 a 和 b 之间添加了两个字节的填充。

Update Russ Cox has kindly written to explain that width is unrelated to alignment. You can read his comment below.

空结构

现在我们已经探索了宽度,应该很明显空结构的宽度为零。它占用零字节的存储空间。

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 的正交性一样,空结构是一种与其他结构类型一样的结构类型。普通结构的所有属性同样适用于空结构。

可以声明一个 structs{} 数组,但它们当然不占用存储空间,即使个数很多

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

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

struct{} 的切片仅占用其切片标头的空间。如上所示,他们的后备阵列不占用空间。

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

当然,正常的 subslice、len 和 cap 内置函数可以按预期工作。

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

可以获取 struct{} 值的地址,当它可寻址时,就像任何其他值一样。

var a struct{}
var b = &a

有趣的是,两个 struct{} 值的地址可能是相同的。

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

此属性对于 []struct{} 也是可见的。

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

note: this property is not required by the spec, but it does note that Two distinct zero-size variables may have the same address in memory.

空结构作为函数接收器

现在我们已经证明了空结构的行为就像任何其他类型一样,因此我们可以将它们用作方法接收器。

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 的确切地址可能会有所不同。

Related Posts:

  1. Struct composition with Go
  2. Friday pop quiz: the smallest buffer
  3. Simple extended attribute support for Go
  4. Padding is hard
赞赏
Nemo版权所有丨如未注明,均为原创丨本网站采用BY-NC-SA协议进行授权,转载请注明转自:https://nemo.cool/955.html
# #
首页      Dev      C++/Go      翻译 || 总结 - Go语言中的空结构体(The empty struct)

Nemo

文章作者

发表回复

textsms
account_circle
email

翻译 || 总结 - Go语言中的空结构体(The empty struct)
翻译 || 总结 - Go语言中的空结构体(The empty struct) 总结 在Go语言中,结构体是一种用户自定义的数据类型。通常情况下,结构体中包含一个或多个字段,每个字段都有一个对应的数据类型…
扫描二维码继续阅读
2023-04-04