一文帶你感受Go語言空結構體的魔力
前言
在 Go
語言中,有一種特殊的用法可能讓許多人感到困惑,那就是空結構體 struct{}
。在本文中,我將對 Go
空結構體進行詳解,準備好了嗎?準備一杯你最喜歡的飲料或茶,隨著本文一探究竟吧。
什么是空結構體
不包含任何字段的結構體,就是空結構體。它有以下兩種定義方式:
匿名空結構體
var e sruct{}
命名空結構體
type EmptyStruct struct{} var e EmptyStruct
空結構體的特點
空結構體主要有以下幾個特點:
- 零內存占用
- 地址相同
- 無狀態(tài)
零內存占用
空結構體不占用任何內存空間,這使得空結構體在內存優(yōu)化方面非常有用,我們來通過例子看看是否真的是零內存占用:
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
。
地址相同
無論創(chuàng)建多少個空結構體,它們所指向的地址都相同的。
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 }
無狀態(tài)
由于空結構體不包含任何字段,因此它不能有狀態(tài)。這使得空結構體在表示無狀態(tài)的對象或情況時非常有用。
為什么是零內存和地址相同
要理解為什么空結構體在內存上是零大?。銉却妫┎⑶叶鄠€空結構體的地址是相同的,需要深入研究 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
是一個用于分配零字節(jié)對象的基準地址,它不占用任何實際的內存空間。
空結構體的使用場景
空結構體主要有以下三種使用場景:
- 實現
Set
集合類型 - 用于通道信號
- 作為方法接收器
實現 Set 集合類型
在 Go
語言中,雖然沒有內置 Set
集合類型,但是我們可以利用 map
類型來實現一個 Set
集合。由于 map
的 key
具有唯一性,我們可以將元素存儲為 key
,而 value
沒有實際作用,為了節(jié)省內存,我們可以使用空結構體作為 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
之間的信號傳遞,尤其是不關心通道中傳遞的具體數據,只需要一個觸發(fā)信號時。例如,我們可以使用空結構體通道來通知一個 Goroutine
停止工作:
package main import ( "fmt" "time" ) func main() { quit := make(chanstruct{}) gofunc() { // 模擬工作 fmt.Println("工作中...") time.Sleep(3 * time.Second) // 關閉退出信號 close(quit) }() // 阻塞,等待退出信號被關閉 <-quit fmt.Println("已收到退出信號,退出中...") }
在這個例子中,創(chuàng)建了一個通道 quit
,并在一個單獨的 Goroutine
中模擬執(zhí)行工作。在完成工作后,關閉了 quit
通道,表示退出信號。主函數在 <-quit
處阻塞,直到收到退出信號,然后打印一條消息并退出程序。
由于通道使用的類型是空結構體,因此不會帶來額外的內存開銷。
在 Go
標準庫中,context
包中的 Context
接口的 Done()
方法返回一個通道信號,用于通知相關操作的完成狀態(tài)。這個通道信號的返回值就是使用了空結構體。
type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chanstruct{} Err() error Value(key any) any }
作為方法接收器
有時候我們需要創(chuàng)建一組方法集的實現(一般來說是實現一個接口),但并不需要在這個實現中存儲任何數據,這種情況下,我們可以使用空結構體來實現:
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
的指針;
最后列舉了空結構體的三個使用場景,通過這些代碼示例,展示了空結構體在實際應用中的一些常見用途。
到此這篇關于一文帶你感受Go語言空結構體的魔力的文章就介紹到這了,更多相關Go語言空結構體內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!