Make 还是 New?Go开发者的永恒问题
你写Go代码已经一周或一年了,但有时在选择make()或new()时还是会犹豫片刻?这很正常。在Stack Overflow上,“Why would I make() or new()?”这个问题获得了数万次浏览,即使在2025年,它依然具有现实意义。为什么?因为在这看似简单的选择背后,隐藏着Go内存管理哲学的根本差异。
让我们一劳永逸地弄清楚这个问题,这样你就不用再猜测了。我们将从基础讲起,涵盖实际案例,指出陷阱,并提供一个日常工作的检查清单。开始吧!
简短回答(给赶时间的人)
如果你需要为切片(slice)、映射(map)或通道(channel)分配内存——请使用make()。在所有其他情况下,使用new()或字面量&Type{}。但魔鬼,总是在细节中。
什么是new()以及它如何工作
new(T)函数是Go的内置函数,它为类型T分配内存,将其清零(零值),并返回指向该内存的指针:*T。
简单来说:new()给你一个空但有效的对象。你得到一个指针,但如果对象需要初始化内部结构,它本身还不能完全正常工作。
package main
import "fmt"
type Person struct { Name string Age int}
func main() { p := new(Person) fmt.Println(p) // 输出:&{ 0} fmt.Println(p.Name == "") // true}这里,new(Person)为结构体分配了内存,将字段清零(空字符串和0),并返回了指针。一切正常。
什么是make()及其超能力
make()也是一个内置函数,但它只适用于三种类型:切片、映射和通道。为什么只有它们?因为这些类型具有需要初始化的内部结构(头部),而不仅仅是清零。
make()不返回指针。它返回所需类型的已初始化值(不是指针!)。
// 切片:长度5,容量10s := make([]int, 5, 10)fmt.Println(len(s), cap(s)) // 5 10
// 映射:准备好插入元素m := make(map[string]int)m["key"] = 42fmt.Println(m) // map[key:42]
// 通道:缓冲大小为3ch := make(chan int, 3)ch <- 1fmt.Println(<-ch) // 1请注意:如果你尝试写m := new(map[string]int),你将得到一个指向nil映射的指针。向这样的映射中插入元素会导致恐慌(panic)。
为什么new()不适用于切片、映射和通道?
让我们以切片为例。Go中的切片是一个由三个字段组成的结构体:指向数组的指针、长度和容量。new([]int)会为该结构体分配内存并将其清零。指针将为nil,长度和容量为0。你将得到&[]——一个指向nil切片的指针。它不会在按索引赋值时崩溃吗?不,它会崩溃。
而make([]int, 3)做了三件事:
- 在底层分配一个包含3个元素的实际数组(如果指定了容量,则可能更多)。
- 初始化切片头部(pointer, length, capacity)。
- 返回一个准备好使用的切片。
映射也是如此:make(map[string]int)创建一个哈希表并为桶分配内存。new(map[string]int)只提供一个nil映射。
2025年何时使用new()?
Go社区当前的实践是:new()很少使用。大多数情况下,开发者更喜欢使用带取地址的字面量:&Type{}。这更具可读性,并且允许立即设置字段。