一文带你感受Go语言空结构体的魔力

 更新时间:2023年05月23日 09:17:07   作者:Go技术干货  
在 Go 语言中,有一种特殊的用法可能让许多人感到困惑,那就是空结构体,本文将对Go空结构体进行详解,准备一杯你最喜欢的饮料或茶,随着本文一探究竟吧

前言

在 Go 语言中,有一种特殊的用法可能让许多人感到困惑,那就是空结构体 struct{}。在本文中,我将对 Go 空结构体进行详解,准备好了吗?准备一杯你最喜欢的饮料或茶,随着本文一探究竟吧。

什么是空结构体

不包含任何字段的结构体,就是空结构体。它有以下两种定义方式:

匿名空结构体

var e sruct{}

命名空结构体

type EmptyStruct struct{}
var e EmptyStruct

空结构体的特点

空结构体主要有以下几个特点:

  • 零内存占用
  • 地址相同
  • 无状态

零内存占用

空结构体不占用任何内存空间,这使得空结构体在内存优化方面非常有用,我们来通过例子看看是否真的是零内存占用:

package main

import (
   "fmt"
   "unsafe"
)

func main() {
   var a int
   var b string
   var e struct{}
   fmt.Println(unsafe.Sizeof(a)) // 4
   fmt.Println(unsafe.Sizeof(b)) // 8
   fmt.Println(unsafe.Sizeof(e)) // 0
}

通过打印结果对比可知,空结构体内存占用为 0

地址相同

无论创建多少个空结构体,它们所指向的地址都相同的。

package main

import (
   "fmt"
)

func main() {
   var e struct{}
   var e2 struct{}
   fmt.Printf("%p\n", &e)  // 0x90b418
   fmt.Printf("%p\n", &e2) // 0x90b418
   fmt.Println(&e == &e2)    // true
}

无状态

由于空结构体不包含任何字段,因此它不能有状态。这使得空结构体在表示无状态的对象或情况时非常有用。

为什么是零内存和地址相同

要理解为什么空结构体在内存上是零大小(零内存)并且多个空结构体的地址是相同的,需要深入研究 Go 的源码。

/go/src/runtime/malloc.go

// base address for all 0-byte allocations
var zerobase uintptr
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    ······
    if size == 0 {
       return unsafe.Pointer(&zerobase)
    }
    ······

根据 malloc.go 源码的部分内容,当要分配的对象大小 size 为 0 时,会返回指向 zerobase 的指针。zerobase 是一个用于分配零字节对象的基准地址,它不占用任何实际的内存空间。

空结构体的使用场景

空结构体主要有以下三种使用场景:

  • 实现 Set 集合类型
  • 用于通道信号
  • 作为方法接收器

实现 Set 集合类型

在 Go 语言中,虽然没有内置 Set 集合类型,但是我们可以利用 map 类型来实现一个 Set 集合。由于 map 的 key 具有唯一性,我们可以将元素存储为 key,而 value 没有实际作用,为了节省内存,我们可以使用空结构体作为 value 的值。

package main
import"fmt"
type Set[K comparable] map[K]struct{}
func (s Set[K]) Add(val K) {
   s[val] = struct{}{}
}
func (s Set[K]) Remove(val K) {
   delete(s, val)
}
func (s Set[K]) Contains(val K) bool {
   _, ok := s[val]
   return ok
}
func main() {
   set := Set[string]{}
   set.Add("陈明勇")
   fmt.Println(set.Contains("陈明勇")) // true
   set.Remove("陈明勇")
   fmt.Println(set.Contains("陈明勇")) // false
}

用于通道信号

空结构体常用于 Goroutine 之间的信号传递,尤其是不关心通道中传递的具体数据,只需要一个触发信号时。例如,我们可以使用空结构体通道来通知一个 Goroutine 停止工作:

package main  
import (  
   "fmt"
   "time"
)  
func main() {  
   quit := make(chanstruct{})  
   gofunc() {  
      // 模拟工作  
      fmt.Println("工作中...")  
      time.Sleep(3 * time.Second)  
      // 关闭退出信号  
      close(quit)
   }()  
   // 阻塞,等待退出信号被关闭  
   <-quit  
   fmt.Println("已收到退出信号,退出中...")  
}

在这个例子中,创建了一个通道 quit,并在一个单独的 Goroutine 中模拟执行工作。在完成工作后,关闭了 quit 通道,表示退出信号。主函数在 <-quit 处阻塞,直到收到退出信号,然后打印一条消息并退出程序。

由于通道使用的类型是空结构体,因此不会带来额外的内存开销。

在 Go 标准库中,context 包中的 Context 接口的 Done() 方法返回一个通道信号,用于通知相关操作的完成状态。这个通道信号的返回值就是使用了空结构体。

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chanstruct{}
    Err() error
    Value(key any) any
}

作为方法接收器

有时候我们需要创建一组方法集的实现(一般来说是实现一个接口),但并不需要在这个实现中存储任何数据,这种情况下,我们可以使用空结构体来实现:

type Person interface {
   SayHello()
   Sleep()
}
type CMY struct{}
func (c CMY) SayHello() {
   fmt.Println("你好,我叫陈明勇。")
}
func (c CMY) Sleep() {
   fmt.Println("陈明勇睡觉中...")
}

这个例子定义了一个接口 Person 和一个结构体 CMY ,并为 CMY 实现了 Person 接口,定义了一组方法(SayHello 和 Sleep)。

由于 CMY 结构体为空结构体,因此不会带来额外的内存开销。

小结

在本文中,首先介绍了 Go 语言 空结构体 的概念和定义方式,它有两种定义方式;

随后对 空结构体 的特点进行介绍,包括其零内存和多个变量地址相同的特性;

接着进一步深入源码,探究了为什么空结构体在 Go 语言中是零内存且多变量地址相同,原因是当要分配的对象大小 size 为 0 时,会返回指向 zerobase 的指针;

最后列举了空结构体的三个使用场景,通过这些代码示例,展示了空结构体在实际应用中的一些常见用途。

到此这篇关于一文带你感受Go语言空结构体的魔力的文章就介绍到这了,更多相关Go语言空结构体内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言并发编程 sync.Once

    Go语言并发编程 sync.Once

    这篇文章要介绍的是Go语言并发编程 sync.Once,sync.Once用于保证某个动作只被执行一次,可用于单例模式中,下面文章我们来介绍一下它的使用方法,需要的朋友可以参考一下
    2021-10-10
  • golang读取yaml配置文件的方法实现

    golang读取yaml配置文件的方法实现

    本文主要介绍了golang读取yaml配置文件的方法实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-10-10
  • 详解Golang如何优雅的终止一个服务

    详解Golang如何优雅的终止一个服务

    后端服务通常会需要创建子协程来进行相应的作业,但进程接受到终止信号或正常结束时,并没有判断或等待子协程执行结束,下面这篇文章主要给大家介绍了关于Golang如何优雅的终止一个服务的相关资料,需要的朋友可以参考下
    2022-03-03
  • Go语言中重构的技巧分享

    Go语言中重构的技巧分享

    这篇文章主要来和大家分享一下Go语言中重构的技巧,即如何尽量避免使用 else、break 和 continue,从而让代码更透明、更易读,感兴趣的小伙伴可以学习一下
    2023-10-10
  • Web框架Gin中间件实现原理步骤解析

    Web框架Gin中间件实现原理步骤解析

    这篇文章主要为大家介绍了Web框架Gin中间件实现原理步骤解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Golang使用反射的动态方法调用详解

    Golang使用反射的动态方法调用详解

    Go是一种静态类型的语言,提供了大量的安全性和性能。这篇文章主要和大家介绍一下Golang使用反射的动态方法调用,感兴趣的小伙伴可以了解一下
    2023-03-03
  • Go中sync.Once源码的深度讲解

    Go中sync.Once源码的深度讲解

    sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有所帮助
    2025-01-01
  • go语言中range用法

    go语言中range用法

    这篇文章主要介绍了go语言中range用法,实例分析了Go语言中range的功能及使用技巧,需要的朋友可以参考下
    2015-03-03
  • 一文带你了解Go语言标准库strings的常用函数和方法

    一文带你了解Go语言标准库strings的常用函数和方法

    strings 库包含了许多高效的字符串常用操作的函数和方法,巧用这些函数与方法,能极大的提高我们程序的性能。本文就来和大家分享一下Go标准库strings的常用函数和方法,希望对大家有所帮助
    2022-11-11
  • 一文了解Go 并发与并行

    一文了解Go 并发与并行

    并发性和并行性是是两个既有联系又有所区别的概念,本文主要介绍了Go并发与并行,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05

最新评论