Go 面試題: New 和 Make 是什么,差異在哪?
本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉載本文請聯系腦子進煎魚了公眾號。
大家好,我是煎魚。
在 Go 語言中,有兩個比較雷同的內置函數,分別是 new 和 make 方法,其主要用途都是用于分配相應類型的內存空間。
看上去 new 和 make 都是分配內存的,那他們有什么區別呢?這個細節點也成為了不少 Go 語言工程師的面試題之一,值得大家一看。
在這篇文章中我們將來解答這個問題。
基本特性
make
在 Go 語言中,內置函數 make 僅支持 slice、map、channel 三種數據類型的內存創建,其返回值是所創建類型的本身,而不是新的指針引用。
函數簽名如下:
- func make(t Type, size ...IntegerType) Type
具體使用示例:
- func main() {
- v1 := make([]int, 1, 5)
- v2 := make(map[int]bool, 5)
- v3 := make(chan int, 1)
- fmt.Println(v1, v2, v3)
- }
在代碼中,我們分別對三種類型調用了 make 函數進行了初始化。你會發現有的入參是有多個長度指定,有的沒有。
這塊的區別主要是長度(len)和容量(cap)的指定,有的類型是沒有容量這一說法,因此自然也就無法指定。
輸出結果:
- [0] map[] 0xc000044070
有一個細節點要注意,調用 make 函數去初始化切片(slice)的類型時,會帶有零值,需要明確是否需要。
見過不少的小伙伴在這上面踩坑。
new
在 Go 語言中,內置函數 new 可以對類型進行內存創建和初始化。其返回值是所創建類型的指針引用,與 make 函數在實質細節上存在區別。
函數簽名如下:
- func new(Type) *Type
具體使用示例:
- type T struct {
- Name string
- }
- func main() {
- v := new(T)
- v.Name = "煎魚"
- }
從上面的例子的效果來看,是不是似曾相似?其實與下面這種方式的一樣的:
- func main() {
- v := T{}
- v.Name = "煎魚"
- }
輸出結果均是:
- &{Name:煎魚}
其實 new 函數在日常工程代碼中是比較少見的,因為他可被替代。
一般會直接用快捷的 T{} 來進行初始化,因為常規的結構體都會帶有結構體的字面屬性:
- func NewT() *T {
- return &T{Name: "煎魚"}
- }
這種初始化方式更方便。
區別在哪里
可能會有的小伙伴會疑惑一點,就是 new 函數也能初始化 make 的三種類型:
- v1 := new(chan bool)
- v2 := new(map[string]struct{})
那 make 函數的區別,優勢是什么呢?
本質上在于 make 函數在初始化時,會初始化 slice、chan、map 類型的內部數據結構,new 函數并不會。
例如:在 map 類型中,合理的長度(len)和容量(cap)可以提高效率和減少開銷。
更進一步的區別:
- make 函數:
- 能夠分配并初始化類型所需的內存空間和結構,返回引用類型的本身。
- 具有使用范圍的局限性,僅支持 channel、map、slice 三種類型。
- 具有獨特的優勢,make 函數會對三種類型的內部數據結構(長度、容量等)賦值。
- new 函數:
- 能夠分配類型所需的內存空間,返回指針引用(指向內存的指針)。
- 可被替代,能夠通過字面值快速初始化。
總結
在這篇文章中,我們介紹了 Go 語言中 make 和 new 函數的使用,并針對其區別點進行了分析。
可能會有小伙伴疑惑,那 new 和 make 函數所初始化出來的內存,是分配在堆還是棧上呢?
這就涉及到 Go 語言中的 “逃逸分析” 了(我公眾號前幾天的文章有發),如果所初始化的變量不需要在當前作用域外生存,那么理論上就不需要初始化在堆上。