深入理解 Go 中的 new() 和 make() 函數
在 Go 語言中,new() 和 make() 是兩個常用的函數,用于創建和初始化不同類型的變量。本文將深入探討 new() 和 make() 的區別、使用場景以及底層實現原理。
一、引言
- Go 中的 new() 和 make() 函數是用于創建和初始化變量的重要工具。
- new() 用于創建指定類型的零值變量,并返回該變量的指針。
- make() 用于創建并初始化引用類型的變量,如切片、映射和通道。
二、new() 函數
- new() 函數的基本語法及用法。
- new() 創建的變量是指定類型的零值,并返回該變量的指針。
- new() 適用于創建引用類型以外的其他類型變量。
package main
import "fmt"
func main() {
// 使用 new() 創建一個 int 類型的零值變量的指針
numPtr := new(int)
fmt.Println(*numPtr) // 輸出 0
}
三、make() 函數
- make() 函數的基本語法及用法。
- make() 用于創建并初始化引用類型的變量。
- make() 適用于創建切片、映射和通道等引用類型的變量。
- make() 創建的變量不是零值,而是根據類型進行初始化。
package main
import "fmt"
func main() {
// 使用 make() 創建一個切片,并初始化長度為 3 的切片
slice := make([]int, 3)
fmt.Println(slice) // 輸出 [0 0 0]
}
四、new() 和 make() 的區別
- new() 用于創建任意類型的變量,而 make() 僅用于創建引用類型的變量。
- new() 返回的是指針,而 make() 返回的是初始化后的值。
- new() 創建的變量是零值,make() 創建的變量是根據類型進行初始化。
package main
import "fmt"
func main() {
// 使用 new() 創建一個結構體的指針
personPtr := new(Person)
personPtr.Name = "Alice"
personPtr.Age = 30
fmt.Println(personPtr) // 輸出 &{Alice 30}
// 使用 make() 創建一個映射,并初始化鍵值對
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // 輸出 map[one:1 two:2]
}
type Person struct {
Name string
Age int
}
五、new() 和 make() 的底層實現原理
在 Go 語言中,new() 和 make() 的底層實現原理略有不同。
1.new() 的底層實現原理
- new() 函數在底層使用了 Go 的 runtime.newobject 函數。
- runtime.newobject 函數會分配一塊內存,大小為指定類型的大小,并將該內存清零。
- 然后,runtime.newobject 函數會返回這塊內存的指針。
下面是 new() 函數的簡化版本的底層實現原理示例代碼:
package main
import (
"fmt"
"unsafe"
)
func main() {
// 使用 new() 創建一個 int 類型的零值變量的指針
numPtr := new(int)
// 獲得指針的值
ptrValue := uintptr(unsafe.Pointer(numPtr))
// 輸出指針的值
fmt.Println(ptrValue)
}
在上述示例代碼中,我們使用了 unsafe 包中的 Pointer 和 uintptr 類型來操作指針。我們首先使用 new(int) 創建一個 int 類型的零值變量的指針 numPtr,然后通過 unsafe.Pointer 將指針轉換為 unsafe.Pointer 類型,再通過 uintptr 將 unsafe.Pointer 值轉換為 uintptr 類型,最后輸出指針的值。這個值就是我們所創建的變量的內存地址。
2.make() 的底層實現原理
- make() 函數在底層使用了 Go 的 runtime.makeslice、runtime.makemap 和 runtime.makechan 函數。
- runtime.makeslice 函數用于創建切片,它會分配一塊連續的內存空間,并返回切片結構體。
- runtime.makemap 函數用于創建映射,它會分配一塊哈希表內存,并返回映射結構體。
- runtime.makechan 函數用于創建通道,它會分配一塊通道內存,并返回通道結構體。
下面是 make() 函數的簡化版本的底層實現原理示例代碼:
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
// 使用 make() 創建一個切片,并初始化長度為 3 的切片
slice := make([]int, 3)
// 獲得切片的值和長度
sliceValue := reflect.ValueOf(slice)
sliceData := sliceValue.Elem().UnsafeAddr()
sliceLen := sliceValue.Len()
// 輸出切片的值和長度
fmt.Println(sliceData, sliceLen)
}
在上述示例代碼中,我們使用了 reflect 包中的 Value、Elem 和 UnsafeAddr 方法來操作切片。我們首先使用 make([]int, 3) 創建一個長度為 3 的切片 slice,然后通過 reflect.ValueOf 將切片轉換為 reflect.Value 類型,再通過 Elem 方法獲取切片的元素,并通過 UnsafeAddr 方法獲取切片的底層數組的指針,最后通過 Len 方法獲取切片的長度。這樣,我們就可以獲得切片的底層數組的指針和長度。
請注意,上述示例代碼中使用了 reflect 和 unsafe 包,這是為了演示 make() 的底層實現原理而引入的,實際開發中并不需要經常使用這些包。
總結
通過深入了解 new() 和 make() 函數的區別、使用場景以及底層實現原理,讀者可以更好地理解和運用這兩個函數,并完美解決掉面試官的問題,并在實際開發中做出準確的選擇。