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 面向对象:结构体。