一文带你感受Go语言空结构体的魔力
作者:Go技术干货 发布时间:2024-05-05 09:30:15
前言
在 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
的指针;
最后列举了空结构体的三个使用场景,通过这些代码示例,展示了空结构体在实际应用中的一些常见用途。
来源:https://mp.weixin.qq.com/s/rD3pnjWF5_Zz-r6GB0LrrA
猜你喜欢
- import sysfrom PyQt5 import QtWidgetsfrom PyQt5.QtWidgets import QMain
- 解决方案:1、选择Edit Configurations, 删除相关单元测试2、右击__name__ == "__main__&q
- 在修改后的文字后面加上: self.textEdit_6.moveCursor(QTextCursor.End)例子:self.textEd
- 最近越来越多在博客上写些UX相关的内容作为分享,就涉及到跟普通博文不一样的文章建构问题。文章内容固然很重要,但排版、组织也是提高可读性和用户
- 比如有一个需求,通过sql语句,返回-5至5的随机整数.如果这一个放在PHP中,则非常简单直接用print rand(-5,5);?>
- 主要使用json模块,直接导入import json即可。小例子如下:#coding=UTF-8 import json info={} i
- 在python中,一个文件(以“.py”为后缀名的文件)就叫做一个模块,每一个模块在python里都
- 回文利用python 自带的翻转 函数 reversed()def is_plalindrome(string): return
- 各大云计算提供商(亚马逊、谷歌和微软)目前都使用了键/值存储方式。然而,在San Francisco召开的MSDN开发者大会上,微软宣布他们
- 前言Django附带的认证对于大多数常见情况来说已经足够了,但是如何在 Django 中使用自定义的数据表进行用户认证,有一种较为笨蛋的办法
- 往列表头部和尾部添加元素往头部添加元素list.insert(index,new_element)@@@index为新元素的插入位置,当in
- 在最开始的时候所有的斐波那契代码都是使用递归的方式来写的,递归有很多的缺点,执行效率低下,浪费资源,还有可能会造成栈溢出,而递归的程序的优点
- DML、DDL、DCL区别 . 总体解释: DML(data manipulation language): 它们是SELECT、UPDAT
- 本文研究的主要是Python之reload流程的相关内容,具体如下。在Python中,reload() 用于重新载入之前载入的模块。relo
- 前言玩博客一个多月了,渐渐发现了一些有意思的事,经常会有人用同样的评论到处刷,不知道是为了加没什么用的积分,还是纯粹为了表达楼主好人。那么问
- 使用FFmpeg命令拼接多个mp3格式的音频文件时报错抛出异常,使用命令格式如下:ffmpeg -i 1.mp3 -i 2.mp3 -fil
- 本文实例讲述了python飞机大战pygame碰撞检测实现方法。分享给大家供大家参考,具体如下:目标了解碰撞检测方法碰撞实现01. 了解碰撞
- 对python中的控制条件、循环和跳出详解代码缩进(代码块):python用缩进表示代码块,没有其他语言的大括号缩进是强制检查,整个代码缩进
- 前言:这篇文章给大家介绍了怎样用python创建一个简单的报警,它可以运行在命令行终端,它需要分钟做为命令行参数,在这个分钟后会打印”wak
- 如何用Python搞到小姐姐私房照本文纯技术角度出发,教你如何用Python爬虫获取百度图库海量照片——技术无罪。学会获取小姐姐私房照同理可