面試官:Golang 的 new 與make 區別是什麼?

後端時光發表於2022-04-14

本篇文章基於 Golang 1.17.2

胖虎坐在公司工位上正在吃剛才買的包子。

新來的小學妹就匆忙地跑過來,慌慌張張說:“學長,不好了,線上程式碼出現問題了。”

胖虎趕緊放下包子,來不及擦嘴,迅速掏出電腦,邊開啟電腦邊問“你知道哪裡報錯嗎,為什麼報錯嗎”

學妹:“不知道啊……”

胖虎:“……行吧,我自己看下吧。”

img

學妹從來沒有見過如此嚴肅的學長,大氣也不敢喘,也不敢走,就默默的看學長在查問題。

胖虎:“找到了,這是誰寫的程式碼啊,map 使用了 new 初始化”


map1 := new(map[string]string)

學妹:“是我,學長……” 胖虎:“行吧,看在你是我學妹份上,今天跟你簡單科普一下吧”

img

變數宣告的方式


var test1 int

我們可以透過 var+變數名稱+變數型別 進行宣告變數,當我們沒有給它賦值的時候,它們的結果是變數型別的零值。

比如說 string 的零值是””, int 的零值是0,引用型別的零值是nil。

以上兩種型別我們可以直接使用,但如果把它改成指標會怎麼樣呢?


package main

import "fmt"

func main() {

var test *string

fmt.Println(test)

*test = "測試"

}

執行結果如下:

img

這是為什麼呢,因為對於引用型別的變數,不僅要宣告,並且還要給它分配記憶體。怎麼給它分配記憶體呢?這就要用到了new了

什麼是new

new 是 Golang 的內建函式,原始碼如下:

img

大意是,分配記憶體的內建函式,第一個引數是型別,而不是具體的值,返回值是該型別的指標。分配的值是該型別零值的指標。

“我知道怎麼改了” 學妹興奮的說道,說完便在編輯器加了兩行程式碼。


package main

import "fmt"

func main() {

var test *string

fmt.Println(test)

test = new(string)

*test = "測試"

fmt.Println(*test)

}

胖虎:“恩,不錯,學得挺快的嘛,那我再問你一下,複合型別的slice、map、chan使用 new 後可以使用嗎?為什麼呢”

學妹:“這你剛才沒說啊,我不知道”

胖虎:“我們們可以敲下程式碼,演示一下嘛”


package main

import "fmt"

func main() {

testMap := new(map[string]string)

(*testMap)["aa"] = "aa"

fmt.Println(testMap)

}

執行下程式碼,竟然報錯了,“這是為什麼呢?”

img

胖虎:“真相只有一個,那就是,map 底層是結構體,這樣說,你可能不理解,舉個?吧。”

Talk is cheap. Show me the code


package main

import "fmt"

type XueMei struct {

age *int64 `json:"age"`

BoyFiriendYn bool `json:"boy_firiend_yn"`

}

func main() {

test := new(XueMei)

//是否有男朋友

test.BoyFiriendYn = false

//此處程式碼會導致panic

//panic: runtime error: invalid memory address or nil pointer dereference

//[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x108a0e9]

*test.age = 1

}

也就是說它裡面的成員變數仍未進行初始化,所以它們幾個初始化要使用make來進行。

學妹崇拜的眼光“ 學長你懂得真多,你還能說說什麼是 make 嗎?”

什麼是make

make 也是用於記憶體分配的內建函式,但是和new不同,原始碼如下圖所示。

img

大意是make內建函式分配並初始化一個slice、map或chan型別的物件。像new函式一樣,第一個引數是型別,而不是值。

與new不同,make的返回型別與其引數的型別相同,而不是指向它的指標。結果的取決於傳入的型別。

並且 slice在 make 的時候,第二個引數必須傳遞,也就是切片的長度。否則會編譯失敗。

new函式底層實現

new函式底層主要是呼叫go1.17/src/runtime/malloc.go中的 newobject 方法。

img

編輯切換為居中

新增圖片註釋,不超過 140 字(可選)

這裡可以看到 newobject 方法,底層呼叫 mallocgc 方法的時候,needzero 傳的是 true ,所以返回值是傳入型別零值的指標。

make函式底層實現

透過執行以下命令go tool compile -N -l -S file.go

我們可以看到make函式初始化

slice呼叫的是runtime.makeslice、runtime.makeslice64這兩個方法.


func  makeslice(typ *byte, len int, cap int) unsafe.Pointer

func  makeslice64(typ *byte, len int64, cap int64) unsafe.Pointer

編輯切換為居中

新增圖片註釋,不超過 140 字(可選)

map呼叫的是runtime.makemap64、runtime.makemap、runtime.makemap_small這三個方法.


func makemap64(mapType *byte, hint int64, mapbuf *any) (hmap map[any]any)

func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)

func makemap_small() (hmap map[any]any)

chan分別呼叫的是runtime.makechan64、runtime.makechan這三個方法.


func makechan64(chanType *byte, size int64) (hchan chan any)

func makechan(chanType *byte, size int) (hchan chan any)

感興趣的同學可以去看下原始碼

學妹:懂了學長,那我總結一下吧

image-20220413104313336

胖虎:“恩,不錯不錯,果然沒有看錯你,今天晚上有時間嗎,跟大家分享一下你今天學到的知識吧。”

學妹:“改天可以嗎?晚上男朋友約我一起吃飯了”

胖虎os:當初面試時候她說沒有男朋友,才把她招進來的,怎麼現在突然有男朋友了?沒想到小丑竟然是我自己

img

本作品採用《CC 協議》,轉載必須註明作者和本文連結
關注微信公眾號:後端時光

相關文章