空结构体struct{}的应用

package main

import (
   "fmt"
   "time"
   "unsafe"
)

// Empty 空结构体
type Empty struct{}

// EmptyMultiple 由多个空结构体组成的结构体
type EmptyMultiple struct {
   s1 Empty
   s2 Empty
   s3 Empty
}

func main() {
   struct0 := struct{}{}      // 定义空结构体类型变量
   struct1 := Empty{}         // 定义空结构体类型变量
   struct2 := EmptyMultiple{} // 定义由多个空结构体组合形成的结构体类型变量
   fmt.Printf("struct0 --- pointer:%p,size:%d \n", &struct0, unsafe.Sizeof(struct0))
   fmt.Printf("struct1 --- pointer:%p,size:%d \n", &struct1, unsafe.Sizeof(struct1))
   fmt.Printf("struct2 --- pointer:%p,size:%d \n", &struct2, unsafe.Sizeof(struct2))
   // struct0 --- pointer:0x3193f8,size:0
   // struct1 --- pointer:0x3193f8,size:0
   // struct2 --- pointer:0x3193f8,size:0
   // 重要说明①:空结构体的指针都是一样的。
   // 重要说明②:无论是单个空结构体,还是由多个空结构体组合形成的新结构体,它们的变量都不占用内存空间。

   // ========== 使用场景①:实现set集合 ========== //
   set := map[string]struct{}{} // 初始化map数据类型变量,其中value的数据类型为空结构体
   // 重要说明:set集合可以理解为只使用key的map,所以value的数据类型设置为int、bool、string等也能实现,
   //           既然如此那么使用不占用内存空间的空结构体则是最优方案。

   set["红楼梦"] = struct{}{} // set集合只需使用key,无需使用value,故将value设为空结构体
   set["西游记"] = struct{}{} // set集合只需使用key,无需使用value,故将value设为空结构体
   set["水浒传"] = struct{}{} // set集合只需使用key,无需使用value,故将value设为空结构体
   // 重要说明:上面三个元素的value都不占用内存空间

   // 检查《三国演义》是否存在
   book := "三国演义"
   value, exist := set[book] // 这里实际上就是检查指定key的map元素是否存在
   if exist {
      fmt.Printf("《%+v》存在(size=%d) \n", book, unsafe.Sizeof(value))
   } else {
      fmt.Printf("《%+v》不存在 \n", book) // 《三国演义》不存在
   }

   // 检查《西游记》是否存在
   book = "西游记"
   value, exist = set[book] // 这里实际上就是检查指定key的map元素是否存在
   if exist {
      fmt.Printf("《%+v》存在(size=%d) \n", book, unsafe.Sizeof(value)) // 《西游记》存在(size=0)
   } else {
      fmt.Printf("《%+v》不存在 \n", book)
   }

   // ========== 使用场景②:空结构体类型管道 ========== //
   channel := make(chan struct{})
   // 重要说明:这里管道的数据类型设置为int、bool、string等也一样可以实现功能,只是这个管道仅用来传递信号,
   //           数据类型是什么不重要,数据值是什么也无所谓,那么使用不占用内存空间的空结构体则是最优方案。

   go func() {
      time.Sleep(time.Second * 3)
      channel <- struct{}{} // 往管道写入空结构体
   }()

   fmt.Println("协程任务执行中……") // 协程任务执行中……

   <-channel // 重要提醒:这里会发生阻塞

   fmt.Println("协程任务已执行完毕") // 协程任务已执行完毕
}

// ========== 总结 ========== //
// 1、空结构体类型的变量不占用内存空间,而且不限于单个空结构体,由多个空结构体组合形成的结构体也不占用内存空间。
// 2、无论什么场景,使用空结构体都是围绕其不占用内存空间的特性做文章,这些场景的共同特征是不关心数据的类型是什么,也不关心
//    数据的值是什么。

Copyright © 2024 码农人生. All Rights Reserved