在Go语言里,new()和make()都用于内存分配,但设计目标完全不同。理解它们的差异,是写出稳定Go程序的基础,尤其在处理slice、map和channel时,选错函数会直接导致运行时崩溃。

new()的工作机制
new()是Go内置的一个函数,接收一个类型参数,为该类型分配零值内存,并返回指向这块内存的指针。它不关心类型内部结构,只是单纯地把内存清零。比如new(int)返回一个*int,指向值为0的int;new(struct)返回结构体指针,字段全是对应零值。
从实现角度看,new(T)等价于&T{}的取地址行为,但语法上更明确表达分配意图。由于它返回的是指针,调用者需要通过*p解引用访问。下面的代码展示了new的基本用法:
package main
import "fmt"
func main() {
p := new(int)
fmt.Println(*p) // 输出0
*p = 10
fmt.Println(*p) // 输出10
type User struct {
Name string
Age int
}
u := new(User)
fmt.Println(u.Name, u.Age) // 输出空字符串 0
}
可以看到,new对任何类型一视同仁,都只是分配并清零。它不会为slice建立底层数组,也不会为map创建哈希表头,这正是和make的关键分水岭。
make()的专门用途
make()只用于三种内置引用类型:slice、map、channel。它不但分配内存,还会初始化类型内部的数据结构,让返回值处于可用状态。以slice为例,make会分配底层数组,并设置长度与容量;map会建立哈希表运行时结构;channel会创建缓冲队列或同步变量。
make的返回值不是指针,而是类型本身(如[]int、map[string]int、chan int)。这与new截然不同。下面分别展示三种类型的make用法:
package main
import "fmt"
func main() {
// slice
s := make([]int, 3, 5)
s[0] = 1
fmt.Println(s, len(s), cap(s))
// map
m := make(map[string]int)
m["a"] = 1
fmt.Println(m)
// channel
c := make(chan int, 2)
c <- 10
fmt.Println(<-c)
}
如果这里把map换成new(map[string]int),得到的是*map[string]int,且指向nil,直接(*m)["a"]=1会panic。只有make能产出非nil且可写的map。
哪些类型必须用make
必须用make的类型只有三个:slice、map、channel。它们是Go运行时特殊管理的引用类型,零值nil不代表可用。new可以给它们分配指针,但无法初始化内部描述符,因此不具备读写能力。
具体规则如下:
- slice:必须用make或字面量(如
[]int{1,2})创建,new得到的*[]int指向nil slice,无法直接下标赋值。 - map:必须用make或字面量(如
map[string]int{})创建,new得到nil map指针,写入会panic。 - channel:必须用make创建并指定缓冲大小或0,new得到nil chan指针,收发操作永久阻塞或panic。
其他所有类型,包括基础类型、数组、结构体、指针等,都可以用new或直接字面量。数组长度固定,new([3]int)返回可用指针;结构体用new也能正常字段赋值。因此,make的强制范围严格限定在上述三类。
常见误区与对比总结
很多初学者认为new和make只是语法糖差异,其实不然。下面的表格列出核心区别:
| 对比项 | new(T) | make(T, args) |
|---|---|---|
| 适用类型 | 任意类型 | 仅slice、map、channel |
| 返回值 | *T(指针) | T(类型本身) |
| 内存状态 | 零值,无内部初始化 | 已初始化,可用 |
| nil安全性 | 返回非nil指针,但可能指向nil引用 | 返回非nil可用对象 |
一个典型错误是试图用new创建map后直接赋值:
package main
func main() {
m := new(map[string]int)
// 下面这行会panic,因为*m是nil map
(*m)["x"] = 1
}
正确做法是用make(map[string]int)。在代码评审中,看到引用类型创建应优先确认是否用了make。掌握这条边界,能显著减少Go程序中的nil pointer和nil map赋值panic。