在 go 中對某種類型進行初始化時會用到 make
和 new
, 由於它們的功能類似,因此初學者可能對它們的感到困惑;本文將由淺入深的介紹其功能和區別golang
長話短說,先放上結論:markdown
方法 | 做用 | 做用對象 | 返回值 |
---|---|---|---|
new | 分配內存 | 值類型和用戶定義的類型 | 初始化爲零值,返回指針 |
make | 分配內存 | 內置引用類型(map, slice, channel) | 初始化爲零值,返回引用類型自己 |
以上爲 make
和 new
的區別,若是有人追問能說的更詳細點嗎?數據結構
哦豁,這就很尷尬了app
短話長說,咱們看看 make
和 new
究竟作了什麼函數
先說要點,new 用來分配內存,並初始化零值,返回零值指針oop
在編譯過程當中,使用 new 大體會產生 2 種狀況:源碼分析
若該對象申請的空間爲 0,則返回表示空指針的 zerobase
變量,這類對象好比:slice
, map
, channel
以及一些結構體等。ui
// path: src/runtime/malloc.go
// base address for all 0-byte allocations
var zerobase uintptr
複製代碼
其餘狀況則會使用 runtime.newobject
函數:spa
// path: src/runtime/malloc.go
func newobject(typ *_type) unsafe.Pointer {
return mallocgc(typ.size, typ, true)
}
複製代碼
這一塊的內容也比較簡單, runtime.newoject
調用了 runtime.mallocgc
函數去開闢一段內存空間,而後返回那塊空間的地址指針
這裏能夠作個簡單的例子去驗證一下:
func main() {
a := new(map[int]int)
fmt.Println(*a) // nil, 參考狀況 1
b := new(int)
fmt.Println(*b) // 0, 參考狀況 2
}
複製代碼
說到底 new
實現什麼功能呢,能夠這樣去理解
// a := new(int); 其餘類型以此類比
var a int
return &a
複製代碼
make
是用來初始化 map
, slice
, channel
這幾種特定類型的
在編譯過程當中,用 make
去初始化不一樣的類型會調用不一樣的底層函數:
map
, 調用 runtime.makemap
slice
, 調用 runtime.makeslice
channel
,調用 runtime.makechan
接下來咱們看這些函數的源碼部分,探究它們與 new
的不一樣,若是瞭解這幾種類型的源碼,很容易理解下面的代碼;若是不瞭解這塊內容的同窗能夠跟着註釋走,瞭解流程就能夠了
runtime.makemap
:
// path: src/runtime/map.go
func makemap(t *maptype, hint int, h *hmap) *hmap {
...
// 初始化 Hmap
if h == nil {
h = new(hmap)
}
// 生成 hash 種子
h.hash0 = fastrand()
// 計算 桶 的數量
B := uint8(0)
for overLoadFactor(hint, B) {
B++
}
h.B = B
if h.B != 0 {
var nextOverflow *bmap
// 建立 桶
h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
...
}
return h
}
複製代碼
這裏爲了方便查看,省去了部分代碼。咱們能夠看到這裏的步驟不少, h = new(hmap)
只是其中的一部分
runtime.makeslice
:
// path: src/runtime/slice.go
func makeslice(et *_type, len, cap int) unsafe.Pointer {
// 計算佔用空間和是否溢出
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
// 一些邊界條件處理
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
// panic: len 超出範圍
panicmakeslicelen()
}
// panic: cap 超出範圍
panicmakeslicecap()
}
return mallocgc(mem, et, true)
}
複製代碼
這裏其實和 new
底層的 runtime.newobject
很類似了,只是這裏多了一些異常處理
runtime.makechan
:
// path: src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
...
var c *hchan
// 針對不一樣狀況下對 channel 實行不一樣的內存分配策略
switch {
case mem == 0:
// 無緩衝區,只給 hchan 分配一段內存
c = (*hchan)(mallocgc(hchanSize, nil, true))
c.buf = c.raceaddr()
case elem.ptrdata == 0:
// channel 不包含指針,給 hchan 和 緩衝區分配一段連續的內存
c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
c.buf = add(unsafe.Pointer(c), hchanSize)
default:
// 單獨給 hchan 和 緩衝區分配內存
c = new(hchan)
c.buf = mallocgc(mem, elem, true)
}
// 初始化 hchan 的內部字段
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
...
}
複製代碼
這裏省略了部分代碼,包括一些異常處理
總之,make
相對於 new
來講,作的事情更多,new
只是開闢了內存空間, make
爲更加複雜的數據結構開闢內存空間並對一些字段進行初始化
注意:有心細的同窗能夠發現,runtime,makemap
, runtime,makeslice
, runtime.makechan
返回的是指針類型,但並不意味着用 make 初始化後,返回的是指針類型,這裏上面列出來的是比較核心部分的源碼,並非全部的源碼
想了解 make 的更多內容,你們能夠嘗試看一下 map
, slice
和 channel
的源碼
這裏有 2 個問題能夠幫你們更好的去理解:
能夠用 new 去初始化 map, slice 和 channel 嗎?
首先咱們回憶一下 new
的功能,簡單理解以下:
var i int
return &int
複製代碼
若是咱們要去初始化上面幾種類型要怎麼去作:
m := *new(map[int]int) // 先取值,由於 new 返回的是指針
s := *new([]int)
ch := *new(chan int)
複製代碼
在上述代碼中,使用 new
去初始化這幾個類型,是不會 panic 的
針對上述代碼的狀況,咱們能夠分類討論:
能夠用 make 去初始化其餘類型嗎,如 int, string ?
不能夠,由於 make 沒有對其餘類型提供相應的底層方法
以上,因爲能力有限,疏忽和不足之處難以免,歡迎讀者指正,以便及時修改。
若本文對你有幫助的話,歡迎 點贊👍 和 轉發,感謝支持!
make 和 new
map 源碼分析
slice 源碼分析
channel 源碼分析