Go语言中的new和make的区别

1,929 阅读3分钟

概述:

在 Go 语言中,makenew 是两个用于内存分配的内置函数,但它们的使用场景和返回值有所不同,下面我们从基本概念、使用场景、代码示例和源码层面分别介绍他们的区别。

基本概念:

new 函数

  • 作用:new 是一个分配内存的内置函数,它为类型 T 分配零值内存,并返回指向该内存的指针
  • 返回值new(T) 返回的是 *T,即指向类型 T 的指针。
  • 适用类型new 适用于所有类型,包括基本类型、结构体、数组等。
  • 示例
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // 使用 new 分配一个 Person 结构体,并返回指针
    p := new(Person)
    fmt.Println(p)        // 输出: &{ 0}
    fmt.Println(*p)       // 输出: { 0}
    fmt.Println(p.Name)   // 输出: ""
    fmt.Println(p.Age)    // 输出: 0

    // 修改结构体的值
    p.Name = "Alice"
    p.Age = 30
    fmt.Println(*p)       // 输出: {Alice 30}

    // 使用 new 分配一个 int 类型,并返回指针
    i := new(int)
    fmt.Println(i)        // 输出: 0x... (指针地址)
    fmt.Println(*i)       // 输出: 0
    *i = 10
    fmt.Println(*i)       // 输出: 10
}

make 函数

  • 作用:make 也是一个分配内存的内置函数,但是它仅仅用于创建特定类型的对象,如 slice、map、channel
  • 返回值make 返回的是一个特定类型的初始化后的值,是经过初始化之后的特定类型的引用。
  • 适用类型make 仅适用于 slicemapchannel 类型。
  • 示例
package main

import "fmt"

func main() {
    // 创建一个长度为 5 的 int 切片
    s := make([]int, 5)
    fmt.Println(s)        // 输出: [0 0 0 0 0]
    s[0] = 10
    fmt.Println(s)        // 输出: [10 0 0 0 0]

    // 创建一个容量为 10 的切片
    s2 := make([]int, 5, 10)
    fmt.Println(s2)       // 输出: [0 0 0 0 0]
    fmt.Println(len(s2))  // 输出: 5
    fmt.Println(cap(s2))  // 输出: 10

    // 创建一个 map
    m := make(map[string]int)
    fmt.Println(m)        // 输出: map[]
    m["a"] = 1
    m["b"] = 2
    fmt.Println(m)        // 输出: map[a:1 b:2]

    // 创建一个无缓冲的 channel
    ch := make(chan int)
    go func() {
        ch <- 1
    }()
    fmt.Println(<-ch)     // 输出: 1

    // 创建一个带缓冲的 channel
    ch2 := make(chan int, 2)
    ch2 <- 10
    ch2 <- 20
    fmt.Println(<-ch2)    // 输出: 10
    fmt.Println(<-ch2)    // 输出: 20
}

使用场景:

new 的使用场景

  • 分配结构体:当你需要一个指向结构体的指针时,可以使用 new。
type Person struct {
    Name string
    Age  int
}

p := new(Person)
  • 分配基本类型:当你需要一个指向基本类型的指针时,可以使用 new。
i := new(int)

make 的使用场景:

  • 创建切片:当你需要一个切片时,可以使用 make。
s := make([]int, 5)  // 创建一个长度为 5 的 int 切片
  • 创建映射:当你需要一个映射时,可以使用 make。
m := make(map[string]int)  // 创建一个 string 到 int 的映射
  • 创建通道:当你需要一个通道时,可以使用 make。
ch := make(chan int)  // 创建一个 int 类型的通道

源码层面:

new 的源码实现

new 的实现非常简单,它直接调用了 runtime.newobject 函数。

// src/runtime/malloc.go
func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}
  • newobject 函数会根据类型的大小 typ.size 分配内存,并返回一个指向该内存的指针。
  • 分配的内存会被初始化为该类型的零值。

make 的源码实现

make 的实现稍微复杂一些,因为它需要处理不同类型的初始化。

  • 切片:make([]T, len, cap) 会调用 runtime.makeslice 函数。
// src/runtime/slice.go
func makeslice(et *_type, len, cap int) unsafe.Pointer {
    // 计算需要的内存大小并分配内存
    // 初始化切片结构体
}
  • 映射:make(map[K]V, hint) 会调用 runtime.makemap 函数。
// src/runtime/map.go
func makemap(t *maptype, hint int, h *hmap) *hmap {
    // 初始化哈希表结构
}
  • 通道:make(chan T, size) 会调用 runtime.makechan 函数。
// src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
    // 初始化通道结构
}

总结

  • new:用于分配内存并返回指向该内存的指针,适用于任何类型,但不会进行额外的初始化。
  • make:用于创建 slice、map 和 channel,不仅分配内存,还会进行初始化,返回的是值而不是指针。