
make 只能用于 slice、map、chan,new 能用于任意类型
这是最常被混淆的起点:不是语法限制,而是设计意图不同。make 创建的是“可直接使用的复合类型实例”,背后会做初始化(比如 slice 的底层数组分配、map 的哈希表初始化);new 只做一件事——分配零值内存并返回指针,不区分类型。
常见错误现象:new([]int) 编译通过,但得到的是一个指向 []int 零值(即 nil slice)的指针,后续调用 append 会 panic;而 make([]int, 0) 返回的是可安全追加的空 slice。
-
make([]int, 5)→ 长度为 5、容量为 5 的 slice,元素全为 0 -
new([]int)→*[]int,解引用后是nil,不能直接用 -
make(map[string]int)→ 已初始化、可直接map[key] = val -
new(map[string]int)→*map[string]int,解引用后仍是nil,赋值 panic
new(T) 等价于 &T{},但 make(T, args) 没有字面量等价写法
理解这个等价关系能快速判断该用哪个:new(int) 就是 &int(0),new(struct{a int}) 就是 &struct{a int}{} 。它纯粹是“取零值地址”的语法糖。
而 make 的参数语义因类型而异,无法靠结构体字面量模拟:
立即学习“go语言免费学习笔记(深入)”;
-
make([]T, len)→ 分配长度为len的底层数组 -
make([]T, len, cap)→ 额外指定容量,影响后续append是否扩容 -
make(map[T]V)→ 初始化哈希表,避免 nil map 写入 panic -
make(chan T)或make(chan T, buffer)→ 创建无缓冲或带缓冲 channel
试图用 &[]int{} 替代 make([]int, 0) 是错的:前者是长度为 0 的非 nil slice,但容量也为 0,第一次 append 必定扩容;后者可预设容量,更可控。
性能与逃逸:make 分配通常逃逸到堆,new 在栈上也可能逃逸
别被“new 更轻量”误导——是否逃逸取决于使用方式,而非函数名。编译器根据变量生命周期和逃逸分析决定。
典型场景:
-
s := make([]int, 1000)→ 大 slice 几乎必然逃逸到堆 -
p := new(int); *p = 42→ 若p不逃逸(比如只在函数内用),int可能分配在栈上 -
return new(int)→ 指针要返回,int必然逃逸到堆 -
return make([]int, 10)→ 同样逃逸,且额外多一次底层数组分配
真正影响性能的是后续操作:用 make([]int, 0, 10) 预设容量,比反复 append 导致多次扩容更省;而 new 后手动构造 slice(如 s := (*[10]int)(unsafe.Pointer(new([10]int)))[:10:10])属于危险操作,不推荐。
什么时候必须用 make,什么时候只能用 new
没有“优先选哪个”的模糊地带,只有明确约束:
- 要创建可用的
slice/map/chan→ 必须用make,new给你的是个废指针 - 需要一个指向零值的指针,且类型不是那三种 → 只能用
new,make编译报错:cannot make type *int - 想初始化 struct 并获取指针 → 用
&MyStruct{...}更直观,new(MyStruct)只给零值,还得逐字段赋值 - 泛型代码中需统一分配接口 →
new可用于任何类型参数T,make无法泛化(因为参数规则随类型变)
最容易被忽略的是:make 的第二个、第三个参数对行为影响极大,但编译器不会警告你传了非法值(比如 make([]int, -1) panic 在运行时);而 new 参数单一,出错成本低,但也掩盖了设计意图——它从不初始化内部状态,这点必须心里有数。









