一文告訴你大神是如何學(xué)習(xí)Go語(yǔ)言之make和new
當(dāng)我們想要在 Go 語(yǔ)言中初始化一個(gè)結(jié)構(gòu)時(shí),其實(shí)會(huì)使用到兩個(gè)完全不同的關(guān)鍵字,也就是 make
和 new
,同時(shí)出現(xiàn)兩個(gè)用于『初始化』的關(guān)鍵字對(duì)于初學(xué)者來(lái)說(shuō)可能會(huì)感到非常困惑,不過(guò)它們兩者有著卻完全不同的作用。
在 Go 語(yǔ)言中,make
關(guān)鍵字的主要作用是初始化內(nèi)置的數(shù)據(jù)結(jié)構(gòu),也就是我們?cè)谇懊嫣岬降?數(shù)組和切片、哈希表 和 Channel,而當(dāng)我們想要獲取指向某個(gè)類(lèi)型的指針時(shí)其實(shí)可以使用 new
關(guān)鍵字,只是知道如何使用 new
的人真的比較少,我們?cè)谶@一節(jié)中就會(huì)介紹 make
和 new
它們的區(qū)別以及實(shí)現(xiàn)原理。
概述
雖然 make
和 new
都是能夠用于初始化數(shù)據(jù)結(jié)構(gòu),但是它們兩者能夠初始化的結(jié)構(gòu)類(lèi)型卻有著較大的不同;make
在 Go 語(yǔ)言中只能用于初始化語(yǔ)言中的基本類(lèi)型:
slice := make([]int, 0, 100) hash := make(map[int]bool, 10) ch := make(chan int, 5)
這些基本類(lèi)型都是語(yǔ)言為我們提供的,我們?cè)谇懊娴恼鹿?jié)中其實(shí)已經(jīng)介紹過(guò)了它們初始化的過(guò)程以及原理,但是在這里還是需要提醒各位讀者注意的是,這三者返回了不同類(lèi)型的數(shù)據(jù)結(jié)構(gòu):
slice
是一個(gè)包含data
、cap
和len
的結(jié)構(gòu)體;hash
是一個(gè)指向hmap
結(jié)構(gòu)體的指針;ch
是一個(gè)指向hchan
結(jié)構(gòu)體的指針;
而另一個(gè)用于初始化數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵字 new
的作用其實(shí)就非常簡(jiǎn)單了,它只是接收一個(gè)類(lèi)型作為參數(shù)然后返回一個(gè)指向這個(gè)類(lèi)型的指針:
i := new(int) var v int i := &v
上述代碼片段中的兩種不同初始化方法其實(shí)是等價(jià)的,它們都會(huì)創(chuàng)建一個(gè)指向 int
零值的指針。
到了這里我們對(duì) Go 語(yǔ)言中這兩種不同關(guān)鍵字的使用也有了一定的了解:make
用于創(chuàng)建切片、哈希表和管道等內(nèi)置數(shù)據(jù)結(jié)構(gòu),new
用于分配并創(chuàng)建一個(gè)指向?qū)?yīng)類(lèi)型的指針。
實(shí)現(xiàn)原理
接下來(lái)我們將分別介紹 make
和 new
在初始化不同數(shù)據(jù)結(jié)構(gòu)時(shí)的具體過(guò)程,我們會(huì)從編譯期間和運(yùn)行時(shí)兩個(gè)不同的階段理解這兩個(gè)關(guān)鍵字的原理,不過(guò)由于前面已經(jīng)詳細(xì)地介紹過(guò) make
的實(shí)現(xiàn)原理,所以我們會(huì)將重點(diǎn)放在 new
上從 Go 語(yǔ)言的源代碼層面分析它的實(shí)現(xiàn)。
make
在前面的章節(jié)中我們其實(shí)已經(jīng)談到過(guò) make
在創(chuàng)建 數(shù)組和切片、哈希表 和 Channel 的具體過(guò)程,所以在這一小節(jié)中,我們也只是會(huì)簡(jiǎn)單提及 make
相關(guān)的數(shù)據(jù)結(jié)構(gòu)初始化原理。
在編譯期間的 類(lèi)型檢查 階段,Go 語(yǔ)言其實(shí)就將代表 make
關(guān)鍵字的 OMAKE
節(jié)點(diǎn)根據(jù)參數(shù)類(lèi)型的不同轉(zhuǎn)換成了 OMAKESLICE
、OMAKEMAP
和 OMAKECHAN
三種不同類(lèi)型的節(jié)點(diǎn),這些節(jié)點(diǎn)最終也會(huì)調(diào)用不同的運(yùn)行時(shí)函數(shù)來(lái)初始化數(shù)據(jù)結(jié)構(gòu)。
new
內(nèi)置函數(shù) new
會(huì)在編譯期間的 SSA 代碼生成 階段經(jīng)過(guò) callnew
函數(shù)的處理,如果請(qǐng)求創(chuàng)建的類(lèi)型大小時(shí) 0,那么就會(huì)返回一個(gè)表示空指針的 zerobase
變量,在遇到其他情況時(shí)會(huì)將關(guān)鍵字轉(zhuǎn)換成 newobject
:
func callnew(t *types.Type) *Node { if t.NotInHeap() { yyerror("%v is go:notinheap; heap allocation disallowed", t) } dowidth(t) if t.Size() == 0 { z := newname(Runtimepkg.Lookup("zerobase")) z.SetClass(PEXTERN) z.Type = t return typecheck(nod(OADDR, z, nil), ctxExpr) } fn := syslook("newobject") fn = substArgTypes(fn, t) v := mkcall1(fn, types.NewPtr(t), nil, typename(t)) v.SetNonNil(true) return v }
需要提到的是,哪怕當(dāng)前變量是使用 var
進(jìn)行初始化,在這一階段可能會(huì)被轉(zhuǎn)換成 newobject
的函數(shù)調(diào)用并在堆上申請(qǐng)內(nèi)存:
func walkstmt(n *Node) *Node { switch n.Op { case ODCL: v := n.Left if v.Class() == PAUTOHEAP { if prealloc[v] == nil { prealloc[v] = callnew(v.Type) } nn := nod(OAS, v.Name.Param.Heapaddr, prealloc[v]) nn.SetColas(true) nn = typecheck(nn, ctxStmt) return walkstmt(nn) } case ONEW: if n.Esc == EscNone { r := temp(n.Type.Elem()) r = nod(OAS, r, nil) r = typecheck(r, ctxStmt) init.Append(r) r = nod(OADDR, r.Left, nil) r = typecheck(r, ctxExpr) n = r } else { n = callnew(n.Type.Elem()) } } }
當(dāng)然這也不是絕對(duì)的,如果當(dāng)前聲明的變量或者參數(shù)不需要在當(dāng)前作用域外『生存』,那么其實(shí)就不會(huì)被初始化在堆上,而是會(huì)初始化在當(dāng)前函數(shù)的棧中并隨著 函數(shù)調(diào)用 的結(jié)束而被銷(xiāo)毀。
newobject
函數(shù)的工作就是獲取傳入類(lèi)型的大小并調(diào)用 mallocgc
在堆上申請(qǐng)一片大小合適的內(nèi)存空間并返回指向這片內(nèi)存空間的指針:
func newobject(typ *_type) unsafe.Pointer { return mallocgc(typ.size, typ, true) }
mallocgc
函數(shù)的實(shí)現(xiàn)大概有 200 多行代碼,在這一節(jié)中就不展開(kāi)詳細(xì)分析了,我們會(huì)在后面的章節(jié)中詳細(xì)介紹 Go 語(yǔ)言的內(nèi)存管理機(jī)制。
總結(jié)
到了最后,簡(jiǎn)單總結(jié)一下 Go 語(yǔ)言中 make
和 new
關(guān)鍵字的實(shí)現(xiàn)原理,make
關(guān)鍵字的主要作用是創(chuàng)建切片、哈希表和 Channel 等內(nèi)置的數(shù)據(jù)結(jié)構(gòu),而 new
的主要作用是為類(lèi)型申請(qǐng)一片內(nèi)存空間,并返回指向這片內(nèi)存的指針。
Reference
- Allocation with new
- Allocation with make
- Make and new
到此這篇關(guān)于一文告訴你大神是如何學(xué)習(xí)Go語(yǔ)言之make和new的文章就介紹到這了,更多相關(guān)Go語(yǔ)言make new內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang封裝一個(gè)執(zhí)行命令行的函數(shù)(return?stderr/stdout/exitcode)示例代碼
在?Go?語(yǔ)言中,您可以使用?os/exec?包來(lái)執(zhí)行外部命令,不通過(guò)調(diào)用?shell,并且能夠獲得進(jìn)程的退出碼、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出,下面給大家分享golang封裝一個(gè)執(zhí)行命令行的函數(shù)(return?stderr/stdout/exitcode)的方法,感興趣的朋友跟隨小編一起看看吧2024-06-06golang?gorm框架數(shù)據(jù)庫(kù)的連接操作示例
這篇文章主要為大家介紹了golang?gorm框架數(shù)據(jù)庫(kù)操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04十個(gè)Golang開(kāi)發(fā)中應(yīng)該避免的錯(cuò)誤總結(jié)
Go是一種靜態(tài)類(lèi)型的、并發(fā)的、垃圾收集的編程語(yǔ)言,由谷歌開(kāi)發(fā)。開(kāi)發(fā)人員在編寫(xiě)Go代碼時(shí)總會(huì)有一些常見(jiàn)的錯(cuò)誤,下面是Go語(yǔ)言中需要避免的十大壞錯(cuò)誤,希望對(duì)大家有所幫助2023-03-03golang給函數(shù)參數(shù)設(shè)置默認(rèn)值的幾種方式小結(jié)(函數(shù)參數(shù)默認(rèn)值
在日常開(kāi)發(fā)中我們有時(shí)候需要使用默認(rèn)設(shè)置,下面這篇文章主要給大家介紹了關(guān)于golang給函數(shù)參數(shù)設(shè)置默認(rèn)值的幾種方式小結(jié)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-01-01Go語(yǔ)言web框架Gin響應(yīng)客戶端的方式
Gin是一個(gè)用Go語(yǔ)言編寫(xiě)的web框架,它是一個(gè)類(lèi)似于martini但擁有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍,本文給大家介紹了Go語(yǔ)言web框架Gin響應(yīng)客戶端有哪些方式,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-10-10