Go语言中的new函数

Jicheng's Blog / 2023-07-25 / 原文

在 Go 语言中,new 是一个预定义函数,用于动态分配内存,并返回一个指向该类型零值的指针。它是 Go 语言中用于在堆上分配内存的一种方式,与在栈上分配内存的方式(通过声明变量)不同。

new 函数的语法如下:

func new(Type) *Type

其中,Type 是任意的数据类型,可以是基本数据类型(如 int、float、bool 等)或结构体类型。

new 函数的作用可以详细解释如下:

  1. 动态分配内存:new 函数用于在堆上动态分配内存,返回一个指向新分配内存地址的指针。这样做的好处是,可以在程序运行时根据需要分配不同大小的内存,而不需要提前定义变量。

  2. 初始化为零值:new 函数会将分配的内存初始化为零值,这意味着对于数值类型,分配的内存将为 0,对于布尔类型,分配的内存将为 false,对于字符串类型,分配的内存将为空字符串,对于结构体类型,分配的内存将为结构体的各个字段的零值。

  3. 返回指针:new 函数返回的是一个指向新分配内存地址的指针,这使得我们可以直接操作这块内存而不必担心拷贝数据的开销。

示例:

package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    // 使用 new 函数创建一个指向整数类型的指针
    ptrInt := new(int)
    fmt.Println("Value of ptrInt:", *ptrInt) // 输出:Value of ptrInt: 0

    // 使用 new 函数创建一个指向结构体类型 Point 的指针
    ptrPoint := new(Point)
    fmt.Println("Value of ptrPoint:", *ptrPoint) // 输出:Value of ptrPoint: {0 0}
}

在上面的示例中,我们使用 new 函数创建了一个指向整数类型和结构体类型 Point 的指针。new 函数会自动将分配的内存初始化为零值,因此输出结果中可以看到指针指向的数据都是零值。

在 Go 语言的 new 函数中,不能直接为新分配的内存设置初始值。new 函数只会分配内存并将其初始化为零值。如果你想要为新分配的内存设置初始值,可以使用 make 函数(用于创建切片、映射和通道)或结构体字面量的方式。

  1. 使用 make 函数:
    make 函数用于创建切片、映射和通道,并且可以设置它们的初始值。注意,make 函数只能用于特定的内置数据类型。

示例:

package main

import "fmt"

func main() {
    // 使用 make 函数创建一个切片,并设置初始值
    slice := make([]int, 3) // 创建一个长度为 3 的切片,初始值为 [0, 0, 0]
    fmt.Println("Slice:", slice)

    // 使用 make 函数创建一个映射,并设置初始值
    m := make(map[string]int) // 创建一个空映射,初始值为 map[]
    m["a"] = 1
    m["b"] = 2
    fmt.Println("Map:", m)

    // 使用 make 函数创建一个通道
    ch := make(chan int) // 创建一个无缓冲的整数类型通道
    fmt.Println("Channel:", ch)
}
  1. 使用结构体字面量:
    如果你想为自定义结构体类型的变量设置初始值,可以使用结构体字面量的方式。

示例:

package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    // 使用结构体字面量创建一个 Point 类型变量,并设置初始值
    point := Point{X: 10, Y: 20} // 创建一个 Point 类型变量,X 值为 10,Y 值为 20
    fmt.Println("Point:", point)
}

总结:

  • new 函数只能用于分配内存并将其初始化为零值,无法直接设置初始值。
  • 如果你需要设置初始值,可以使用 make 函数(针对切片、映射和通道)或结构体字面量的方式(针对自定义结构体类型)。

在 Go 语言中,new 函数用于在堆上分配内存,并返回一个指向新分配内存地址的指针。而在函数体内,变量的分配位置有一定的灵活性,既可以在栈上分配内存,也可以在堆上分配内存,具体取决于编译器的优化和变量的生命周期。

  1. new 函数:
    new 函数用于在堆上分配内存,并返回一个指向新分配内存地址的指针。无论变量是基本数据类型还是复杂类型(如结构体),使用 new 函数分配的内存都位于堆上。

示例:

package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    // 使用 new 函数创建一个指向整数类型的指针
    ptrInt := new(int)
    fmt.Printf("Pointer to int: %p\n", ptrInt)

    // 使用 new 函数创建一个指向结构体类型 Point 的指针
    ptrPoint := new(Point)
    fmt.Printf("Pointer to Point: %p\n", ptrPoint)
}

输出:

Pointer to int: 0xc000016080
Pointer to Point: 0xc00000e1e0
  1. 函数体内的变量分配:
    在函数体内,变量的分配位置既可以在栈上,也可以在堆上。编译器根据变量的生命周期来决定变量的分配位置。
  • 栈上分配:对于函数中的局部变量,如果其生命周期在编译期间是可确定的,并且不会逃逸到函数外部(不会在函数返回后继续被引用),那么编译器可能会将其分配在栈上。栈上分配内存速度较快,但存在一些限制,例如栈的大小通常有限制,并且栈上的内存在函数返回后会被自动回收。

  • 堆上分配:如果编译器无法确定变量的生命周期,或者变量逃逸到函数外部,那么它可能会将变量分配在堆上。堆上分配内存相对较慢,但是对于需要在函数返回后继续使用的变量或需要较大内存空间的变量来说是必要的。

示例:

package main

import "fmt"

func foo() *int {
    x := 42
    return &x // 返回局部变量的地址
}

func main() {
    ptr := foo()
    fmt.Println("Value at pointer:", *ptr) // 输出:Value at pointer: 42
}

在上述示例中,foo 函数返回了一个局部变量 x 的地址,该变量在函数执行完后可能继续被引用,所以编译器将 x 分配在堆上。

总结:

  • new 函数用于在堆上分配内存,并返回指向新分配内存的指针。
  • 函数体内的变量分配位置可以是栈上或堆上,根据编译器对变量生命周期的分析和优化决定。对于确定生命周期和未逃逸的局部变量,编译器可能会将其分配在栈上;对于不确定生命周期或逃逸到函数外部的变量,编译器可能会将其分配在堆上。