Go语言--空结构体struct{}解析

wx57f63dceec388 · · 574 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

参考链接: Q语言-属性

简介 

有c/c++学习经历的会发现go的struct语法和c/c++很类型,但是golang的struct{}很有意思。 

做控制而非数据信息: chan struct{}实现set: map[string]struct{}


解析 

结构体是没有位段的结构体,以下是空结构体的一些例子: 

type Q struct{}var q struct{}

但是如果一个就结构体没有位段,不包含任何数据,那么他的用处是什么?我们能够利用空结构体完成什么任务? 

背景 

在深入研究空结构体之前,我想先简短的介绍一下关于结构体宽度的知识。 

术语宽度来自于gc编译器,但是他的词源可以追溯到几十年以前。 

宽度描述了存储一个数据类型实例需要占用的字节数,由于进程的内存空间是一维的,我更倾向于将宽度理解为Size(这个词实在不知道怎么翻译了,请谅解)。 

宽度是数据类型的一个属性。Go程序中所有的实例都是一种数据类型,一个实例的宽度是由他的数据类型决定的,通常是8bit的整数倍。 

我们可以通过unsafe.Sizeof()函数获取任何实例的宽度: 

var s string

var c complex128

fmt.Println(unsafe.Sizeof(s)) // prints 8

fmt.Println(unsafe.Sizeof(c)) // prints 16 

数组的宽度是他元素宽度的整数倍。 

var a [3]uint32

fmt.Println(unsafe.Sizeof(a)) // prints 12 

结构体提供了定义组合类型的灵活方式,组合类型的宽度是字段宽度的和,然后再加上填充宽度。 

type S struct {

a uint16

b uint32

}

var s S

fmt.Println(unsafe.Sizeof(s)) // prints 8, not 6 

空结构体 

现在我们清楚的认识到空结构体的宽度是0,他占用了0字节的内存空间。 

var s struct{}

fmt.Println(unsafe.Sizeof(s)) // prints 0 

由于空结构体占用0字节,那么空结构体也不需要填充字节。所以空结构体组成的组合数据类型也不会占用内存空间。 

type S struct {

A struct{}

B struct{}

}

var s S

fmt.Println(unsafe.Sizeof(s)) // prints 0 

空结构体作用 

由于Go的正交性,空结构体可以像其他结构体一样正常使用。正常结构体拥有的属性,空结构体一样具有。 

你可以定义一个空结构体组成的数组,当然这个切片不占用内存空间。 

var x [1000000000]struct{}

fmt.Println(unsafe.Sizeof(x)) // prints 0 

空结构体组成的切片的宽度只是他的头部数据的长度,就像上例展示的那样,切片元素不占用内存空间。 

var x = make([]struct{}, 1000000000)

fmt.Println(unsafe.Sizeof(x)) // prints 12 in the playground 

当然切片的内置子切片、长度和容量等属性依旧可以工作。 

ar x = make([]struct{}, 100)

var y = x[:50]

fmt.Println(len(y), cap(y)) // prints 50 100 

你甚至可以寻址一个空结构体,空结构体是可寻址的,就像其他类型的实例一样。 

var a struct{}

var b = &a 

有意思的是两个空结构体的地址可以相等。(go 1.12 版本,不相等)  

var a, b struct{}

fmt.Println(&a == &b) // false 

空结构体的元素也具有一样的属性。 

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 

空结构体作为接收者 

现在让我们展示一下空结构体如何像其他结构体工作,空结构体可以作为方法的接收者。 

type S struct{}

func (s *S) addr() { fmt.Printf("%p\n", s) }

func main() {

var a, b S

a.addr() // 0x1beeb0

b.addr() // 0x1beeb0


chan struct{} 

在Go语言中,有一种特殊的struct{}类型的channel,它不能被写入任何数据,只有通过close()函数进行关闭操作,才能进行输出操作。struct{}类型的channel不占用任何内存!!!  定义: 

var sig = make(chan struct{}) 

使用空 struct 是对内存更友好的开发方式,在 go 源代码中针对 空struct 类数据内存申请部分,返回地址都是一个固定的地址。那么就避免了可能的内存滥用。 

栗子: 

package main


import "fmt"

import "time"


var strChan = make(chan string,3)


func main(){

    syncChan1 := make(chan struct{},1)  //接收同步变量  

    syncChan2 := make(chan struct{},2) //主线程启动了两个goruntime线程,

                                       //等这两个goruntime线程结束后主线程才能结束


    //用于演示接受操作

    go func(){

        <- syncChan1  //表示可以开始接收数据了,否则等待

        fmt.Println("[receiver] Received a sync signal and wait a second...")

        time.Sleep(time.Second)

        for{

            if elem,ok := <-strChan;ok{

                fmt.Println("[receiver] Received:",elem)

            }else{

                break

            }

        }

        fmt.Println("[receiver] Stopped.")

        syncChan2 <- struct{}{}

    }()


    //用于演示发送操作

    go func(){

        for i,elem := range []string{"a","b","c","d"}{

            fmt.Println("[sender] Sent:",elem)

            strChan <- elem

            if (i+1)%3==0 {

                syncChan1 <- struct{}{}

                fmt.Println("[sender] Sent a sync signal. wait 1 secnd...")

                time.Sleep(time.Second)

            }

        }

        fmt.Println("[sender] wait 2 seconds...")

        time.Sleep(time.Second)

        close(strChan)

        syncChan2 <- struct{}{}

    }()


    //主线程等待发送线程和接收线程结束后再结束

    fmt.Println("[main] waiting...")

    <- syncChan2

    <- syncChan2

    fmt.Println("[main] stoped")

运行结果: 

[main] waiting...

[sender] Sent: a

[sender] Sent: b

[sender] Sent: c

[sender] Sent a sync signal. wait 1 secnd...

[receiver] Received a sync signal and wait a second...

[receiver] Received: a

[receiver] Received: b

[receiver] Received: c

[sender] Sent: d

[sender] wait 2 seconds...

[receiver] Received: d

[receiver] Stopped.

[main] stoped 

struch{}代表不包含任何字段的结构体类型,也可称为空结构体类型。在go语言中,空结构体类型是不占用系统内存的,并且所有该类型的变量都拥有相同的内存地址。建议用于传递信号的通道都以struct{}作为元素类型,除非需要传递更多的信息发送方向通道发送的值会被复制,接收方接收到的总是该值得副本,而不是该值本身。经由通道传递的值最少会被复制一次,最多会被复制两次。例如,当向一个已空的通道发送值,且已有至少一个接收方因此等待时,该通道会绕过本身的缓冲队列,直接把这个值复制给最早等待的那个接收方,这种情况传递的值只复制一次;当从一个已满的通道接收值,且已有至少一个发送方因此等待时,该通道会把缓冲队列中最早进入的那个值复制给接收方,再把最早等待的发送方要发送的数据复制到那个值得原先位置上(通道的缓冲队列属于环形队列,这样做是没有问题的),这种情况传递的值复制两次。通道传递是复制传递的值。因此如果传递的是值类型,接收方对该值得修改不会影响发送方持有的值;如果传递的是引用类型,则发送方或者接收方对该对象的修改会影响双方所持有的对象


参考:https://dave.cheney.net/2014/03/25/the-empty-struct 

End



有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:wx57f63dceec388

查看原文:Go语言--空结构体struct{}解析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

574 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传