深入理解 Go 中的 new() 和 make() 函式

發表於2023-09-24

在 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() 的底層實現原理略有不同。

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 包中的 Pointeruintptr 型別來操作指標。我們首先使用 new(int) 建立一個 int 型別的零值變數的指標 numPtr,然後透過 unsafe.Pointer 將指標轉換為 unsafe.Pointer 型別,再透過 uintptrunsafe.Pointer 值轉換為 uintptr 型別,最後輸出指標的值。這個值就是我們所建立的變數的記憶體地址。

make() 的底層實現原理

  • make() 函式在底層使用了 Go 的 runtime.makesliceruntime.makemapruntime.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 包中的 ValueElemUnsafeAddr 方法來操作切片。我們首先使用 make([]int, 3) 建立一個長度為 3 的切片 slice,然後透過 reflect.ValueOf 將切片轉換為 reflect.Value 型別,再透過 Elem 方法獲取切片的元素,並透過 UnsafeAddr 方法獲取切片的底層陣列的指標,最後透過 Len 方法獲取切片的長度。這樣,我們就可以獲得切片的底層陣列的指標和長度。

請注意,上述示例程式碼中使用了 reflectunsafe 包,這是為了演示 make() 的底層實現原理而引入的,實際開發中並不需要經常使用這些包。

總結:

透過深入瞭解 new()make() 函式的區別、使用場景以及底層實現原理,讀者可以更好地理解和運用這兩個函式,並完美解決掉面試官的問題,並在實際開發中做出準確的選擇。

本文由mdnice多平臺釋出

相關文章