make仅用于slice、map、chan,new可用于任意类型;make返回类型本身并预分配底层结构,new返回指向零值的指针,对struct或指针类型用make会编译错误。

make 只能用于 slice、map、chan,new 能用于任意类型
这是最常踩的坑:用 make 初始化 struct 或指针变量会直接报错 cannot make type *T 或 cannot make type T。因为 make 的设计目标就是为引用类型预分配底层数据结构——slice 需要数组+长度+容量三元组,map 需要哈希表头,chan 需要队列和锁。而 new 更底层,它只做一件事:申请零值内存并返回指向它的指针。
常见错误现象:
-
make([]int, 0)✅ 合法,返回[]int(非 nil 切片) -
make(map[string]int)✅ 合法,返回可直接使用的map -
make(struct{a int})❌ 编译错误:cannot make type struct { a int } -
new(int)✅ 返回*int,值为0 -
new([]int)✅ 返回*[]int,值为nil指针(注意:不是空切片)
make 返回类型本身,new 总是返回指针
这个差异直接影响你怎么用它们初始化变量。比如你要建一个空但可 append 的切片,必须用 make:
xs := make([]int, 0) // xs 是 []int 类型,len=0,cap=0,可 append
而如果你写:
立即学习“go语言免费学习笔记(深入)”;
ys := new([]int) // ys 是 *[]int 类型,*ys == nil,不能直接 append
这时候你得解引用再赋值:*ys = make([]int, 0),多此一举。所以除非你明确需要一个指向零值的指针(比如传参给函数要求接收 *T),否则别用 new 初始化引用类型。
使用场景差异:
- 想立刻拿到可用的集合容器 → 用
make - 想为某个值类型(如
int、struct)分配内存并获取其地址 → 用new - 函数参数要求
*T且你还没构造好具体值 →new(T)是最简方式
make 的第二个/第三个参数影响底层分配,new 没有这些参数
make 的参数语义因类型而异,但都跟内存布局强相关;new 固定只有一个参数,不接受长度或容量。
例如:
-
make([]int, 5)→ 分配长度为 5 的底层数组,len=5,cap=5 -
make([]int, 3, 10)→ 底层数组长度仍为 10,但切片视图只暴露前 3 个元素,cap=10,后续 append 不触发扩容 -
make(map[int]string, 100)→ 预分配约 100 个桶(bucket),减少 rehash 次数(只是 hint,不保证) -
new(int)→ 就是分配 8 字节(64 位系统),填 0,返回*int,没得选
性能影响明显:用 make([]T, 0, N) 预估容量,比反复 append 再扩容快得多;而 new 几乎无性能调节空间,它就是最朴素的 malloc + zero。
nil 切片 vs new([]*int) 得到的指针
这个点特别容易混淆。一个声明未初始化的切片变量本身就是 nil:
var s []int // s == nil
但 new([]int) 返回的是一个非 nil 的指针,它指向一个 nil 切片:
p := new([]int) // p != nil, *p == nil
这意味着:
-
if s == nil✅ 可判空 -
if p == nil❌ 假,p是有效指针 -
if *p == nil✅ 真,但多数时候你根本不需要这层间接
所以除非你在写反射或 unsafe 相关逻辑,或者封装某个需要统一返回指针的 API,否则不要用 new 去“初始化” slice/map/chan —— 它们本就支持字面量或 make 直接构造,更直观、更安全、更符合 Go 的惯用法。
最常被忽略的其实是:对引用类型而言,new 给你的是“指针的指针”的错觉,而实际开发中你几乎总想要那个引用类型本身,不是它的地址。










