最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang 空結(jié)構(gòu)體特性與用法詳解

 更新時(shí)間:2026年03月02日 10:20:48   作者:戀喵大鯉魚(yú)  
本文主要介紹了Golang 空結(jié)構(gòu)體特性與用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

1.簡(jiǎn)介

在 Go 語(yǔ)言中,空結(jié)構(gòu)體是一個(gè)不包含任何字段的特殊結(jié)構(gòu)體。

空結(jié)構(gòu)體的定義如下:

type EmptyStruct struct{}

2.核心特性

2.1 零內(nèi)存占用

在 Go 中,我們可以使用 unsafe.Sizeof 計(jì)算出一個(gè)數(shù)據(jù)類(lèi)型實(shí)例需要占用的字節(jié)數(shù)。

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Println(unsafe.Sizeof(struct{}{}))
}

運(yùn)行輸出:

$ go run main.go
0

可以看到,Go 中空結(jié)構(gòu)體 struct{} 是不占用內(nèi)存空間,不像 C/C++ 中空結(jié)構(gòu)體仍占用 1 字節(jié)。

2.2 值比較語(yǔ)義

空結(jié)構(gòu)體實(shí)例可進(jìn)行相等性比較,因?yàn)闆](méi)有值,所以同類(lèi)型空結(jié)構(gòu)體的所有實(shí)例在值上是相等的(a == b 恒為 true)。

type EmptyStruct struct{}

var a = EmptyStruct{}
var b = EmptyStruct{}

// 恒等
a == b

2.3 類(lèi)型隔離

不同名稱(chēng)的空結(jié)構(gòu)體代表不同類(lèi)型。

type EmptyStructA struct{}
type EmptyStructB struct{}

var a = EmptyStructA{}
var b = EmptyStructB{}
	
// invalid operation: a == b (mismatched types EmptyStructA and EmptyStructB)
fmt.Println(a == b)

2.4 值地址

空結(jié)構(gòu)體的地址可能相等也可能不等。

當(dāng)空結(jié)構(gòu)體變量未發(fā)生逃逸時(shí),它們的地址是不等的;當(dāng)發(fā)生逃逸時(shí),它們的地址是相等的,都指向了 runtime.zerobase。

詳見(jiàn) Go編程語(yǔ)言規(guī)范 所說(shuō)的:

Pointers to distinct zero-size variables may or may not be equal.

Go 語(yǔ)言比較操作符比較規(guī)則:

注意: 不論逃逸還是未逃逸,我們都不應(yīng)該對(duì)空結(jié)構(gòu)體類(lèi)型變量指向的內(nèi)存地址是否一樣,做任何預(yù)期。

下面以實(shí)例來(lái)驗(yàn)證。

package main

import (
	"fmt"
)

type Empty struct{}

func main() {
    a := &Empty{}
    fmt.Printf("%p\n", a)
    b := &Empty{}
    fmt.Printf("%p\n", b)
    fmt.Println(a == b)
}

很多人認(rèn)為 a 和 b 是 2 個(gè)不同的對(duì)象實(shí)例,便認(rèn)為 a 和 b 具備不同的內(nèi)存地址,故而判斷 a==b 的結(jié)果為 false。我們看一下實(shí)際輸出:

0x2c8460
0x2c8460
true

上面的 a 與 b 地址相同的原因是二者發(fā)生了內(nèi)存逃逸,變量被分配到堆內(nèi)存空間,所以地址都指向了 runtime.zerobase。

倘若我們?nèi)サ羧我庖粋€(gè)(或者將打印內(nèi)存的地址都去掉也一樣),則 a==b 的判斷輸出,就是 false。再看一下代碼:

package main

import (
	"fmt"
)

type Empty struct{}

func main() {
    a := &Empty{}
    // fmt.Printf("%p\n", a)
    b := &Empty{}
    fmt.Printf("%p\n", b)
    fmt.Println(a == b)
}

運(yùn)行輸出:

0x677460
false

那么,可以看出是 fmt.Printf 影響了最終結(jié)果的判斷。我們看一下上面代碼的內(nèi)存逃逸情況分析:

$ go build -gcflags='-m -l' main.go
# command-line-arguments
./main.go:10:10: &Empty{} does not escape
./main.go:12:10: &Empty{} escapes to heap
./main.go:13:15: ... argument does not escape
./main.go:14:16: ... argument does not escape
./main.go:14:19: a == b escapes to heap

可以看到,變量 b 從棧內(nèi)存逃逸到了堆內(nèi)存上。而變量 a 沒(méi)有逃逸。因?yàn)閷?duì) b 使用了 fmt.Printf 而 a 沒(méi)有。由此可以簡(jiǎn)單判斷,是fmt.Printf 導(dǎo)致變量產(chǎn)生了內(nèi)存由棧向堆的逃逸。

這里有兩個(gè)問(wèn)題需要解釋一下:

問(wèn)題1. 為什么 fmt.Printf 會(huì)導(dǎo)致變量的內(nèi)存逃逸?

fmt.Printf的參數(shù)是可變長(zhǎng)度的 interface{} 類(lèi)型(空接口)。當(dāng)你傳遞一個(gè)變量給 fmt.Printf 時(shí),Go 會(huì)將該變量 隱式轉(zhuǎn)換為接口值。空接口值的底層實(shí)現(xiàn)包含兩部分:

  • 類(lèi)型信息(指向具體類(lèi)型的指針)
  • 值信息(指向?qū)嶋H數(shù)據(jù)的指針)

fmt.Printf 導(dǎo)致變量?jī)?nèi)存逃逸的核心原因在于:

  • 接口的隱式轉(zhuǎn)換需要保證值的生命周期。
  • 反射和動(dòng)態(tài)類(lèi)型導(dǎo)致編譯器無(wú)法靜態(tài)分析變量作用域。

問(wèn)題2. 為什么逃逸到了堆內(nèi)存,2 個(gè)變量就一樣了?

這是因?yàn)?,堆上?nèi)存分配調(diào)用了 runtime 包的 newobject 函數(shù)。而 newobject 函數(shù)其實(shí)本質(zhì)上會(huì)調(diào)用 runtime 包內(nèi)的 mallocgc 函數(shù)。這個(gè)函數(shù)有點(diǎn)特別:

// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
	if gcphase == _GCmarktermination {
		throw("mallocgc called with gcphase == _GCmarktermination")
	}

  // 關(guān)鍵部分,如果要分配內(nèi)存的變量不占用實(shí)際內(nèi)存,則直接用 golang 的全局變量 zerobase 的地址。
	if size == 0 {
		return unsafe.Pointer(&zerobase)
	}

  // ...
}

函數(shù)比較長(zhǎng),我做了截取。這函數(shù)內(nèi)有一個(gè)判斷。 如果要分配內(nèi)存的變量不占用實(shí)際內(nèi)存,則直接用 golang 的全局變量 zerobase 的地址。而我們的變量 a 和變量 b 有一個(gè)共同特點(diǎn),就是它們是“空 struct”,空 struct 不占用內(nèi)存空間。

所以,a 和 b 是空 struct,再做內(nèi)存分配的時(shí)候,使用了 golang 內(nèi)部全局私有變量 runtime.zerobase 的內(nèi)存地址。

如何驗(yàn)證 a 和 b 都使用的是 runtime.zerobase 內(nèi)存地址?

我們可以使用 //go:linkname 編譯器指令(Compiler Directive),用于在代碼中顯式聲明一個(gè)符號(hào)(如函數(shù)或變量)與其他包中的未導(dǎo)出(unexported)符號(hào)進(jìn)行鏈接。

語(yǔ)法:

//go:linkname 當(dāng)前符號(hào) 目標(biāo)符號(hào)的全路徑

注意:必須導(dǎo)入 unsafe 包(指令本身并不使用 unsafe,但編譯器要求)。

import _ "unsafe"

下面打印空結(jié)構(gòu)體對(duì)象 和 runtime.zerobase 的內(nèi)存地址。

package main

import (
	"fmt"

	_ "unsafe"
)

//go:linkname zerobase runtime.zerobase
var zerobase uintptr // 使用 go:linkname 編譯指令,將 zerobase 變量與其未導(dǎo)出的 runtime.zerobase 符號(hào)進(jìn)行鏈接

type Empty struct{}

func main() {
	a := &Empty{}
	b := &Empty{}

	fmt.Printf("%p\n", a)
	fmt.Printf("%p\n", b)
	fmt.Printf("%p\n", &zerobase)
}

運(yùn)行輸出(每次結(jié)果不一樣):

$ go run main.go
0x10f7460
0x10f7460
0x10f7460

從上面輸出的內(nèi)容可以看到,空結(jié)構(gòu)體對(duì)象當(dāng)發(fā)生逃逸時(shí),其內(nèi)存地址與全局變量 runtime.zerobase 的地址相同。

3.作用

因?yàn)榭战Y(jié)構(gòu)體不占據(jù)內(nèi)存空間,因此被廣泛作為各種場(chǎng)景下的占位符使用。

一是節(jié)省資源,二是空結(jié)構(gòu)體本身就具備很強(qiáng)的語(yǔ)義,即這里不需要任何值,僅作為占位符,達(dá)到的代碼即注釋的效果。

3.1 實(shí)現(xiàn)集合(Set)

Golang 標(biāo)準(zhǔn)庫(kù)沒(méi)有提供 Set 的實(shí)現(xiàn),通常使用 map 來(lái)代替。事實(shí)上,對(duì)于集合來(lái)說(shuō),只需要 map 的鍵,而不需要值。即使是將值設(shè)置為 bool 類(lèi)型,也會(huì)多占據(jù) 1 個(gè)字節(jié),那假設(shè) map 中有一百萬(wàn)條數(shù)據(jù),就會(huì)浪費(fèi) 1MB 的空間。

因此呢,將 map 作為集合(Set)使用時(shí),可以將值類(lèi)型定義為空結(jié)構(gòu)體,僅作為占位符使用即可。

type Set map[string]struct{}

func (s Set) Has(key string) bool {
	_, ok := s[key]
	return ok
}

func (s Set) Add(key string) {
	s[key] = struct{}{}
}

func (s Set) Delete(key string) {
	delete(s, key)
}

func main() {
	s := make(Set)
	s.Add("foo")
	s.Add("bar")
	fmt.Println(s.Has("foo"))
	fmt.Println(s.Has("bar"))
}

如果想使用 Set 的完整功能,如初始化(通過(guò)切片構(gòu)建一個(gè) Set)、Add、Del、Clear、Contains 等操作,可以使用開(kāi)源庫(kù) golang-set。

3.2 不發(fā)送數(shù)據(jù)的信道

有時(shí)使用 channel 不需要發(fā)送任何數(shù)據(jù),只用來(lái)通知子協(xié)程(goroutine)執(zhí)行任務(wù),或只用來(lái)控制協(xié)程的并發(fā)。

這種情況下,使用空結(jié)構(gòu)體作為占位符就非常合適了。

func worker(ch chan struct{}) {
	<-ch
	fmt.Println("do something")
}

func main() {
	ch := make(chan struct{})
	go worker(ch)
	ch <- struct{}{}
	close(ch)
}

3.3 無(wú)狀態(tài)方法接收者

在 Go 中,方法是一種將函數(shù)與特定類(lèi)型相關(guān)聯(lián)的機(jī)制。如果我們不需要訪(fǎng)問(wèn)方法中的任何接收器字段,那么可以使用空結(jié)構(gòu)體作為接收器類(lèi)型。

type Door struct{}

func (d Door) Open() {
	fmt.Println("Open the door")
}

func (d Door) Close() {
	fmt.Println("Close the door")
}

上面例子中的 Door,事實(shí)上 Door 可以用任何數(shù)據(jù)類(lèi)型而不是空結(jié)構(gòu)體,例如:

type Door int
type Door bool

只不過(guò)無(wú)論是 int 還是 bool 都會(huì)浪費(fèi)額外的內(nèi)存。因此,在這種情況下,聲明為空結(jié)構(gòu)體最合適。

3.4 作為 context 的 value 的 key

空結(jié)構(gòu)體實(shí)例可進(jìn)行相等性比較,因?yàn)闆](méi)有值,所以同類(lèi)型空結(jié)構(gòu)體的所有實(shí)例在值上是相等的。

所以我們可以使用空結(jié)構(gòu)體作為攜帶值的 context 的 value 的 key。

這樣做的好處時(shí),是可以隨時(shí)隨地采用結(jié)構(gòu)體字面量訪(fǎng)問(wèn) context 中的 value,而不需要引用某個(gè)全局變量。

package main

import (
	"context"
	"fmt"
)

// 定義空結(jié)構(gòu)體作為鍵類(lèi)型
type EmptyStructA struct{}
type EmptyStructB struct{}

func main() {
	ctx := context.Background()

	// 將數(shù)據(jù)附加到 context。
	ctx = context.WithValue(ctx, EmptyStructA{}, "EmptyStructA data")
	ctx = context.WithValue(ctx, EmptyStructB{}, "EmptyStructA data")

	// 檢索數(shù)據(jù),可以直接使用結(jié)構(gòu)體字面量檢索,而無(wú)需引用全局變量。
	if value, ok := ctx.Value(EmptyStructA{}).(string); ok {
		fmt.Println(value)
	}
	if value, ok := ctx.Value(EmptyStructB{}).(string); ok {
		fmt.Println(value)
	}
}

運(yùn)行輸出:

EmptyStructA data
EmptyStructA data

4.小結(jié)

Golang 中空結(jié)構(gòu)體 struct{} 是一種特殊的數(shù)據(jù)類(lèi)型,不包含任何字段,其內(nèi)存占用大小為零字節(jié)。這一特性使得它在需要節(jié)省內(nèi)存或表示無(wú)數(shù)據(jù)狀態(tài)的場(chǎng)景中非常有用。

雖然空結(jié)構(gòu)體 struct{} 看似簡(jiǎn)單,但其零內(nèi)存占用和多功能性使其在 Go 編程中有著廣泛的應(yīng)用,特別是在內(nèi)存優(yōu)化和并發(fā)控制場(chǎng)景中。正確利用空結(jié)構(gòu)體可以帶來(lái)代碼效率的提升和資源的節(jié)約。

到此這篇關(guān)于Golang 空結(jié)構(gòu)體特性與用法詳解的文章就介紹到這了,更多相關(guān)Golang 空結(jié)構(gòu)體內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于go微服務(wù)效率工具goctl深度解析

    基于go微服務(wù)效率工具goctl深度解析

    這篇文章主要為大家介紹了基于go微服務(wù)效率工具goctl深度解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • golang定時(shí)器案例詳解

    golang定時(shí)器案例詳解

    定時(shí)器就像一次性鬧鐘,設(shè)置后在指定時(shí)間觸發(fā)一次事件,適合延遲執(zhí)行或超時(shí)控制,本文給大家介紹golang定時(shí)器案例講解,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2025-10-10
  • Golang干貨分享之利用AST實(shí)現(xiàn)AOP功能

    Golang干貨分享之利用AST實(shí)現(xiàn)AOP功能

    本文主要是一個(gè)純干貨分享,主要介紹了Golang如何利用AST實(shí)現(xiàn)AOP功能,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-04-04
  • go mod vendor命令操作

    go mod vendor命令操作

    go mod vendor是Go語(yǔ)言中用于將所有依賴(lài)項(xiàng)的源代碼復(fù)制到vendor目錄的命令,支持創(chuàng)建本地依賴(lài)副本、覆蓋更新、離線(xiàn)構(gòu)建、依賴(lài)版本鎖定和可重復(fù)構(gòu)建等優(yōu)勢(shì),本文介紹go mod vendor命令的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • golangci-lint安裝與Goland集成問(wèn)題

    golangci-lint安裝與Goland集成問(wèn)題

    這篇文章主要介紹了golangci-lint安裝與Goland集成,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • Go 語(yǔ)言中的指針的使用

    Go 語(yǔ)言中的指針的使用

    在Go語(yǔ)言中,指針是存儲(chǔ)另一變量?jī)?nèi)存地址的變量,通過(guò)&操作符獲取變量地址,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-09-09
  • go內(nèi)存緩存如何new一個(gè)bigcache對(duì)象示例詳解

    go內(nèi)存緩存如何new一個(gè)bigcache對(duì)象示例詳解

    這篇文章主要為大家介紹了go內(nèi)存緩存如何new一個(gè)bigcache對(duì)象示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • go中string、int、float相互轉(zhuǎn)換的實(shí)現(xiàn)示例

    go中string、int、float相互轉(zhuǎn)換的實(shí)現(xiàn)示例

    本文主要介紹了go中string、int、float相互轉(zhuǎn)換的實(shí)現(xiàn)示例,文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Go語(yǔ)言函數(shù)學(xué)習(xí)教程

    Go語(yǔ)言函數(shù)學(xué)習(xí)教程

    這篇文章主要介紹了Go語(yǔ)言函數(shù)基本用法,結(jié)合實(shí)例形式分析了Go語(yǔ)言函數(shù)的格式、定義、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2016-07-07
  • Go標(biāo)準(zhǔn)庫(kù)Flag庫(kù)和Log庫(kù)的使用

    Go標(biāo)準(zhǔn)庫(kù)Flag庫(kù)和Log庫(kù)的使用

    本文主要介紹了Go標(biāo)準(zhǔn)庫(kù)Flag庫(kù)和Log庫(kù)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-05-05

最新評(píng)論

中阳县| 永安市| 高唐县| 道真| 静海县| 芮城县| 黄浦区| 田阳县| 墨脱县| 安宁市| 綦江县| 邵阳县| 抚州市| 苍南县| 义乌市| 虎林市| 鸡泽县| 湟中县| 吉隆县| 博罗县| 孙吴县| 大城县| 育儿| 安龙县| 金阳县| 厦门市| 扬中市| 乳源| 长沙县| 新绛县| 剑阁县| 杭锦后旗| 庆阳市| 濮阳市| 东乌| 天全县| 桓仁| 余干县| 吴堡县| 丰城市| 巴中市|