Golang 中 new 与 make 的区别及适用场景

咖啡半糖
咖啡半糖
发布时间:2026-08-25 | 116人浏览过
原创

前言

刚接触 Go 时,new 和 make很容易被当成“两个都能申请内存的函数”来记忆,但一到写结构体、切片或映射时,差别马上就会暴露出来。本文不绕概念,直接从返回值、适用类型和常见报错入手,把它们各自该用在什么地方讲清楚,帮助你在代码里快速判断该选哪一个。

用卡片对照 Go 中 new 和 make 的职责、返回值与适用类型
一眼看懂 new 和 make先把两者最容易混淆的地方拆开:一个返回零值指针,一个返回已初始化的可用结构。

刚接触 Go 时,new 和 make很容易被当成“两个都能申请内存的函数”来记忆,但一到写结构体、切片或映射时,差别马上就会暴露出来。本文不绕概念,直接从返回值、适用类型和常见报错入手,把它们各自该用在什么地方讲清楚,帮助你在代码里快速判断该选哪一个。

new 和 make 的核心区别

在 Go 语言中,new 和 make都和内存分配有关,但它们解决的不是同一类问题。

new做的是“为某个类型分配一块内存,并返回这个类型零值的指针”。它接收的是类型,而不是值,函数签名如下:

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

也就是说,new返回的一定是指针,指向的是该类型的零值。比如 int 的零值是 0,string 的零值是 "",而指针、切片、映射、通道、函数这些引用相关类型的零值则是 nil。

相比之下,make不是单纯分配内存,而是“为特定类型完成初始化”。它只用于 slice、map 和 channel 三种类型,返回的是类型本身,而不是指针。这个差别正是日常开发里最需要优先记住的一点。

new 是什么:返回指针与零值

先看一个最简单的例子:

package main

import (
    "fmt"
)

func main() {
    s := new(string)
    fmt.Println(s) // output: 0xc00012a0b0
    fmt.Println(*s) // output: 
}

new(string)返回的是 *string,也就是字符串指针。打印 s 时看到的是地址,而 *s 对应的值则是字符串零值,也就是空字符串。

这个行为在结构体上也一样:

package main

import (
    "fmt"
)

type User struct {
    Name string
    Age  int
}

func main() {
    var u *User
    u = new(User)
    u.Name = "alice"
    u.Age = 18
    fmt.Println(u) // output: &{alice 18}
    fmt.Println(*u) // output: {alice 18}
    fmt.Println(&u) // 0xc00005a040
}

这里的 u 是 *User。如果只声明不初始化,它默认是 nil,不能直接拿来写字段;经过 new(User) 之后,才有了一块可用内存,接下来才能安全赋值。

不过在实际项目里,new并不是创建结构体指针的唯一写法,更常见的是直接取结构体字面量地址:

package main

type User struct {
    Name string
    Age  int
}

func main() {
    var u *User
    u = &User{}
}

new(User)和&User{}在日常使用里几乎没有本质区别,后者通常更常见,代码也更紧凑,还方便在初始化时直接填写字段值。因此在业务代码中,很多开发者会优先写成 &User{}。

make 为什么只能用在三种类型

make只服务于三类内置引用结构:slice、map 和 channel。原因在于,这三种类型不是只要“有一块内存”就能直接工作,它们还需要完成内部结构的初始化。

看下面的例子:

package main

import "fmt"

func main() {
    s := make([]int, 4, 16) // size: 4, cap: 16
    fmt.Println(s)          // output [0 0 0 0]

    m := make(map[string]int, 4) // size: 4
    m["k0"] = 1
    fmt.Println(m) // output: map[key0:1]

    ch := make(chan int, 1) // size: 1
    ch <- 10
    fmt.Println(<-ch) // output: 10
}

这段代码体现了 make 的三个关键特征:

  • 它只适用于 slice、map、channel。
  • 它返回的是可直接使用的值,而不是指针。
  • 它可以顺带完成初始化参数设置,比如切片长度、切片容量、映射初始空间、通道缓冲大小。

这也是为什么在 Go 项目里,make的出现频率通常远高于 new。切片、映射和通道本来就是高频数据结构,而它们又往往必须靠 make 才能进入可用状态。

用 new 处理 slice、map、channel 会发生什么

很多初学者会继续追问:既然 new 也能分配内存,那它能不能直接拿来创建 slice、map 或 channel?答案是可以写,但通常不能直接用。

展示用 new 创建 map、slice、channel 后仍为 nil 的行为对照图
为什么 new 了还是 nil这一节最关键的不是“分配了没有”,而是“内部结构是否已经初始化”。

先看 map 的例子:

package main

import "fmt"

func main() {
    m := new(map[string]string) // m 的类型为 *map[string]string
    fmt.Println(&m)             // 0xc00005a040
    fmt.Println(*m == nil)      // true
    (*m)["k0"] = "v0"           // panic: assignment to entry in nil map
}

这里 new 返回的是 *map[string]string,也就是“指向 map 的指针”。但要注意,指针指向的那个 map 值仍然是 nil,并没有变成一个已经初始化好的映射。因此一旦写入键值对,就会触发:

panic: assignment to entry in nil map

再看三个类型统一对比的结果:

package main

import "fmt"

func main() {
    a := *new([]int)
    fmt.Printf("%v, %tn", a, a == nil) // output: [], true

    b := *new(map[string]int)
    fmt.Printf("%v, %tn", b, b == nil) // output: map[], true

    c := *new(chan int)
    fmt.Printf("%v, %tn", c, c == nil) // output: <nil>, true
}

无论是 slice、map 还是 channel,new 得到的都只是“这个类型零值的指针”。而这三类类型的零值恰好都是 nil,因此不能等价替代 make 的初始化效果。

这正是两者最容易踩坑的地方:new 解决的是“有无一块内存”,make 解决的是“这个数据结构能不能直接工作”。

开发中怎么选择:new、make 与直接字面量

如果把实际开发场景压缩成一句判断规则,可以这样记:

  • 创建基本类型或结构体指针,优先考虑 new 或 &T{}。
  • 创建 slice、map、channel,优先使用 make。
  • 如果是切片、映射的简单空值初始化,也可以直接使用字面量。

例如:

package main

import "fmt"

func main() {
    s := []int{}
    fmt.Println(s)

    m := map[string]string{}
    fmt.Println(m)
}

这种写法可以替代一部分 make 场景,尤其是在不关心预分配长度、容量时,代码会更直观。

但有一个例外需要单独记住:chan 必须通过 make 初始化。仅仅使用 var 声明通道时,它的默认零值是 nil,不能正常收发数据。

至于 new(User) 和 &User{} 的差别,通常不需要在普通业务代码里过度放大。前者表达“分配一个零值对象并返回指针”,后者表达“创建一个结构体并取地址”,最终效果在大多数场景中都足够接近。真正更重要的,是你是否清楚某个类型拿到零值后能不能直接使用。

归纳起来:

  • new:分配内存,返回指针,得到的是零值对象。
  • make:初始化 slice、map、channel,返回可直接使用的值。

把这条线分清,写 Go 时就不容易在指针、空 map 和 nil channel 上反复踩坑。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表