Skip to content

2.11 结构体:空结构体

空结构体没有任何字段的结构体类型,类型字面量写作 struct{}。常配合具名类型使用,例如 type SetMarker struct{},也可直接使用匿名 struct{}(如 map[string]struct{} 里的 value、chan struct{} 里的元素)。

2.1 结构体 中普通结构体相同:空结构体仍是合法类型,可以定义变量、作为字段/参数类型、实现接口、绑定方法。

一、定义与方法

go
type Lamp struct{} // 无字段

func (l Lamp) On()  { println("on") }
func (l Lamp) Off() { println("off") }

func main() {
	var x Lamp
	x.On()
}

方法挂在类型上,不增加该类型值的大小;下面用 unsafe.Sizeof 看的是值本身占用的空间,与方法集合无关。

二、占用多大空间?

单独的 struct{} 类型变量unsafe.Sizeof 在常见实现上为 0(Go 将空结构体视为 零大小类型 的一种用法):

go
package main

import (
	"fmt"
	"unsafe"
)

type Lamp struct{}

func (l Lamp) On() { fmt.Println("On") }

func main() {
	var lamp Lamp
	fmt.Println(unsafe.Sizeof(lamp)) // 0
}

注意:

  • Sizeof 只统计「这个值」自身,不包含堆分配、切片/映射的内部结构等。例如 []struct{} 仍有切片头等开销,不能说「整个程序里凡是 struct{} 都零字节」。
  • 具体数值以实现为准,业务逻辑不要依赖「一定是 0」;这里强调的是:没有字段可存,常作纯占位

三、常见用法

1. map[K]struct{} 当集合(Set)

只需要「键是否存在」时,用 空结构体 作 value,比 map[K]bool 更省:布尔值至少 1 字节struct{} 不占元素存储空间(映射桶等元数据仍有开销)。

go
seen := make(map[string]struct{})
seen["id1"] = struct{}{}
if _, ok := seen["id1"]; ok {
	// 已存在
}

删除:delete(seen, "id1")

2. chan struct{} 作信号

只需要「通知一下」、不传业务数据时,用 chan struct{},发送 struct{}{}。比 chan bool 语义更清晰(没有「该发 true 还是 false」的歧义),且 value 侧不存有效载荷。

go
done := make(chan struct{})

go func() {
	// ... 工作 ...
	close(done) // 或 done <- struct{}{}
}()

<-done // 等待结束

带缓冲的 make(chan struct{}, 1) 也可用作二值信号量式的同步(与课件中「发一个信号」的写法一致)。

3. 接口与类型参数占位

有时只需要一个区分用的类型标签,或满足泛型/接口约束,也可用空结构体(例如 type T struct{} 再实现某接口),避免捏造无意义的字段。

四、和 bool 占位对比(小结)

方式典型场景说明
map[K]struct{}集合、去重标记value 无数据,语义「只关心 key」
map[K]bool同样可实现集合每个 value 至少 1 字节,且易混用 true/false 含义
chan struct{}纯信号、关闭通知close(ch) 常用于广播「结束」
chan bool可表达两种信号需约定 true/false 含义

五、小结

  • 空结构体写作 struct{},可具名 type X struct{}
  • 可绑定方法;unsafe.Sizeof 对单独 struct{} 变量常见为 0,但不要依赖魔法数字推断其它复合类型的总内存。
  • map[K]struct{}chan struct{} 是 Go 里最常见的两种惯用法:集合信号

若尚未熟悉结构体基础,可先阅读 2.1 面向对象:结构体