$struct$是$Golang$里的关键字,用于定义结构类型
比如
type Student struct{
id int
name string
}
struct{}是有$0$个元素的结构体.
struct{}{}表示类型struct{}的值为空{}
1.性质
1.1不占用内存
大小为$0$,不需要内存来存储struct{}类型的值。
func Test_Size(t *testing.T) {
var x int
var y string
var z struct{
}
t.Log(unsafe.Sizeof(x), unsafe.Sizeof(y), unsafe.Sizeof(z)) //8 16 0
}
1.2 地址相同
定义两个struct{}类型的变量,打印并比较其地址,发现是相同的
func Test_Address(t *testing.T) {
var x struct{
}
var y struct{
}
t.Logf("%p %p %v %v", &x, &y, &x == &y, x == y) // 0x1290460 0x1290460 true true
}
2.用途
2.1实现set
golang里没有实现set,如果在使用map的时候只关心key,不关心value的话,可以借助struct{}来实现set。比如key为string类型,那么set的实现map[string]struct{}。
因为struct{}不占用空间,所以在查找和判断的过程中速度会快,而且占用的内存也会小
简单实现:
func Test_set(t *testing.T) {
set := make(map[string]struct{
})
set["a"] = struct{
}{
}
if _, ok := set["a"]; ok {
t.Log("exists") //exists
}
}
使用interface{}实现
type Set map[interface{
}]struct{
}
func (s Set) Add(item interface{
}) {
s[item] = struct{
}{
}
}
func (s Set) Remove(item interface{
}) {
delete(s, item)
}
func (s Set) Contains(item interface{
}) bool {
_, exists := s[item]
return exists
}
func Test_any_set(t *testing.T) {
set := make(Set)
set.Add("apple")
set.Add("banana")
set.Add("orange")
fmt.Println("Set:", set)
fmt.Println("Contains apple:", set.Contains("apple"))
fmt.Println("Contains grape:", set.Contains("grape"))
set.Remove("banana")
fmt.Println("Set:", set)
}
使用泛型实现:
type Set[T comparable] map[T]struct{
}
func (s Set[T]) Add(v T) {
s[v] = struct{
}{
}
}
func (s Set[T]) Remove(v T) {
delete(s, v)
}
func (s Set[T]) Contains(v T) bool {
_, ok := s[v]
return ok
}
func (s Set[T]) Len() int {
return len(s)
}
func (s Set[T]) Values() []T {
values := make([]T, 0, s.Len())
for v := range s {
values = append(values, v)
}
return values
}
func Test_any_set(t *testing.T) {
s := Set[string]{
}
s.Add("apple")
s.Add("banana")
s.Add("orange")
fmt.Println("Set:", s.Values())
fmt.Println("Contains apple:", s.Contains("apple"))
fmt.Println("Contains grape:", s.Contains("grape"))
s.Remove("banana")
fmt.Println("Set:", s.Values())
}
2.2. 用于无数据的channel
有的时候$channel$不需要发送数据,只需要一个触发信号,就可以使用struct{}来减少信号传递过程中的内存开销
a. 等待协程完成
$worker$函数是一个协程,它会模拟一些工作并在完成后发送空结构体值到$done$通道。
在$Test_wait$函数中,我们通过从$done$通道接收空结构体值来等待工作完成。
func worker(done chan struct{
}) {
fmt.Println("Worker: Performing some work...")
time.Sleep(2 * time.Second)
fmt.Println("Worker: Work completed!")
done <- struct{
}{
} // 发送空结构体值表示工作完成
}
func Test_wait(t *testing.T) {
done := make(chan struct{
})
go worker(done)
<-done // 接收空结构体值,等待工作完成
t.Log("Main: Done!")
}
b.触发事件
$waitForEvent$函数会等待接收到空结构体值,表示事件发生。
$triggerEvent$函数会在一段时间后发送空结构体值到event通道,表示事件发生。
通过使用空结构体值作为通道元素,我们可以简单地实现事件的触发和等待。
func waitForEvent(event chan struct{
}) {
fmt.Println("Waiting for event...")
<-event // 等待接收到空结构体值,表示事件发生
fmt.Println("Event received!")
}
func triggerEvent(event chan struct{
}) {
time.Sleep(2 * time.Second)
fmt.Println("Triggering event...")
event <- struct{
}{
} // 发送空结构体值,表示事件发生
}
func Test_event(t *testing.T) {
event := make(chan struct{
})
go waitForEvent(event)
go triggerEvent(event)
time.Sleep(3 * time.Second)
}
2.3 方法接收器
实现一个接口,只需要实现一些方法,不用声明一些额外的数据,可以用struct{}来实现,也可以换成其他任意的变量,比如$int$ ,$bool$
type Animal interface {
Shouting()
}
type Dog struct{
}
func (dog *Dog) Shouting() {
fmt.Println("wang wang wang")
}