Go 内存分配的“双子星”:为什么有了 `new` 还要 `make`?

举报
golang学习记 发表于 2026/03/06 14:12:19 2026/03/06
【摘要】 导读:Go 语言新手村常有一个未解之谜:既然 new 能分配内存,为什么还要搞个 make?是设计师闲得慌,还是这俩有什么不可告人的秘密?今天我们来扒一扒这对“内存双子星”背后的爱恨情仇。 1. 新手村的困惑:选择困难症当你第一次在 Go 代码里看到这两个家伙时,内心戏通常是这样的:p := new(int) // 嗯,分配一个 int,返回指针,合理。m := make(map[...

导读:Go 语言新手村常有一个未解之谜:既然 new 能分配内存,为什么还要搞个 make?是设计师闲得慌,还是这俩有什么不可告人的秘密?今天我们来扒一扒这对“内存双子星”背后的爱恨情仇。


1. 新手村的困惑:选择困难症

当你第一次在 Go 代码里看到这两个家伙时,内心戏通常是这样的:

p := new(int)      // 嗯,分配一个 int,返回指针,合理。
m := make(map[string]int) // 嗯?分配一个 map,为啥不用 new?

如果你头铁,非要这么写:

m := new(map[string]int) // 编译通过,嘿嘿!
m["key"] = 1             // 💥 运行时 panic: assignment to entry in nil map

啪! 程序挂了。

这时候你才明白,Go 设计师不是有“选择困难症”,而是早就给你挖好了坑,就等你跳进去才知道什么叫**“术业有专攻”**。


2. new:通用的“毛坯房”开发商

new 是 Go 里的通用内存分配器。它的任务非常简单纯粹:

  1. 分配内存:根据类型 T,申请一块足够大的内存。
  2. 清零:把这块内存里的所有比特位都变成 0(零值)。
  3. 返回指针:返回 *T,告诉你“地址在这儿,你自己看着办”。

生活化类比

new 就像是买了一块空地
开发商(编译器)把地圈给你,地上杂草拔光了(清零),然后给了你一张地契(指针)。
但是!地上没有房子,没有水电,没有马桶。

p := new(int)   // 你得到了一块能放 int 的地,值是 0
*p = 10         // 你可以在地上盖个小棚子

对于普通类型(int, struct, array),这块“空地”已经够用了,因为它们的零值就是合法的状态。


3. make:精装房的“交房”管家

make 是 Go 里的特定类型初始化器。它只服务于三个“娇气”的类型:slice(切片)、map(映射)、channel(通道)

它的任务更复杂:

  1. 分配内存:不仅分配对象本身,还要分配内部数据结构所需的内存。
  2. 初始化:把内部指针、长度、容量等字段设置好,让它立刻可用
  3. 返回值:返回 T(注意:不是指针!)。

生活化类比

make 就像是买精装房
管家不仅给你地,还帮你把房子盖好了,水电通了,马桶装好了,钥匙直接塞你手里(值)。
你拿到手就能直接住(直接写入数据),不用自己操心内部结构。

m := make(map[string]int) // 精装房,水电已通
m["key"] = 1              // 直接拎包入住,不会 panic

4. 核心区别:为什么 make 不返回指针?

这是最让初学者晕头转向的地方。

  • new(T) 返回 *T
  • make(T) 返回 T

设计哲学思考:

Slice、Map、Channel 在 Go 内部本身就是引用类型(Reference Types)。

  • 一个 slice 变量内部其实是一个小结构体,里面藏着指向底层数组的指针。
  • 一个 map 变量内部藏着指向哈希表的指针。

如果你用 new(map),你得到的是 *map(指向 map 变量的指针)。这就像是**“指向钥匙的钥匙”**,纯属脱裤子放屁——多此一举。

// ❌ new 的尴尬
m := new(map[string]int) // m 是 *map[string]int
*m = make(map[string]int) // 你还得再 make 一次赋值进去,累不累?

// ✅ make 的优雅
m := make(map[string]int) // m 是 map[string]int,内部已经包含了指针

Go 的设计智慧
对于这三种类型,值本身就已经包含了引用。所以 make 直接返回值,既方便使用,又避免了双重指针的混乱。


5. 背后的设计思想:分配 vs 初始化

Go 语言设计者(Rob Pike 等人)在这里贯彻了一个核心原则:显式优于隐式,分配不等于可用。

1. 分离关注点 (Separation of Concerns)

  • new 关注“内存”:我只管给你腾地方,至于这地方能不能用,我不保证(零值可能不可用)。
  • make 关注“状态”:我保证给你的东西是初始化完毕、立即可用的。

2. 避免隐式开销

如果 new(map) 自动初始化了 map,那么每次你声明 var m map[string]int(零值是 nil)时,系统是不是也得偷偷帮你初始化?
不行!因为 map 初始化是有开销的。Go 希望你按需分配

  • 想要 nil map?var m map...
  • 想要可用 map?make(map...)
  • 这种显式调用,让程序员清楚地知道:“哦,这里发生了一次内存分配和初始化”。

3. 类型系统的诚实

Go 不喜欢魔法。

  • new 诚实地告诉你:这是原始内存。
  • make 诚实地告诉你:这是特殊类型,需要特殊照顾。
    如果强行统一成一个函数,要么导致普通类型被过度初始化(浪费性能),要么导致特殊类型初始化不完全(引发 Panic)。

6. 一张表看懂“双子星”

特性 new(T) make(T, args)
适用类型 所有类型 (int, struct, array…) 仅限 3 种 (slice, map, chan)
返回值 *T (指针) T (值,非指针)
主要动作 内存分配 + 清零 内存分配 + 初始化
结果状态 零值 (Zero Value) 可用状态 (Ready to use)
生活类比 买空地 (毛坯) 买精装房 (拎包入住)
常见错误 对 map/slice 用 new 导致 panic 对 int/struct 用 make 导致编译错误

7. 总结:不要试图挑战设计师的智商

Go 语言里 newmake 并存,不是历史遗留问题,也不是设计失误,而是一次精妙的权衡

  • new 是底层工具:它暴露了内存分配的本质,适用于大多数不需要复杂初始化的类型。
  • make 是高层抽象:它封装了复杂数据结构的初始化细节,让你不用关心底层指针怎么指,只管用。

给开发者的建议:

  1. 日常开发中,make 更常用。因为 slice、map、chan 是 Go 的三大主力数据结构。
  2. new 其实很少用。因为 Go 支持字面量初始化(&T{}new(T) 更直观),或者直接用 var 声明零值。
  3. 记住那个 Panic:当你看到 assignment to entry in nil map 时,请默念三遍:“我该用 make,不该用 new"。

Go 的语言哲学总是这样:简单,但绝不无脑。 它通过这两个函数告诉你:内存是廉价的,但可用的状态是珍贵的。


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。