logo

new与make的差异及使用策略

作者
Modified on
Reading time
4 分钟阅读:..评论:..

Go语言在数据结构初始化时,经常会用到newmake两个内置函数。

这两个函数都能分配内存空间,但它们具体有什么不同?而在编码实践中,如何正确高效地使用newmake呢?

new的内涵

让我们先来探讨new函数:

func new(Type) *Type

new函数用于分配内存。它的第一个参数是类型而非值,返回的是指向该类型新分配的零值的指针。

通过官方定义,我们可以了解到new的以下特性:

  1. 用于内存分配,分配的内存中存储的是类型的零值。
  2. 函数只接受一个参数——即你要分配的内存类型。Go语言中的任何类型都可作为new的参数。
  3. 返回值为指针。

如下代码示例,展示了new(T)与声明变量并取地址的等效性:

// 方式一 ptr := new(T) // 方式二 var t T ptr := &t

需要注意的是,Go的new与C++中的new并不相同:

  • Go的new所分配的内存可能位于栈上,也可能位于堆上,而C++的new总是在堆上分配内存。
  • Go的new只能初始化为类型的零值,不支持显式初始化。而C++的new则可以在分配时指定初始值。
  • 在Go中,由于没有构造函数的概念,new不会调用构造函数。而在C++中,new会调用相应类型的构造函数。

make的本质

现在,让我们来解析make函数:

func make(t Type, size ...IntegerType) Type

make函数专门用来分配并初始化类型为切片(slice)、映射(map)、通道(chan)的对象(仅限这三种)。与new相似,make的第一个参数是类型。不同的是,make返回的类型与其参数相同,并非指针。其结果的规格取决于类型:

切片(Slice): 第二个参数指定长度。切片的容量等于其长度。可以提供第二个整数参数来指定不同的容量;容量不能小于长度。例如,make([]int, 0, 10)分配了一个大小为10的底层数组,并返回一个长度为0、容量为10且基于该数组的切片。

映射(Map): 分配一个空映射,足以容纳指定数量的元素。大小参数可以省略,这样就会分配一个小的初始大小。

通道(Channel): 通道的缓冲区将以指定的容量初始化。如果为零或省略大小,通道将是无缓冲的。

make的定义中我们可知:

  1. 既分配又初始化内存。
  2. 只适用于切片(slice)、映射(map)和通道(chan)三种类型。
  3. 返回的是实际类型,而非指向该类型的指针。

常见疑惑的解答

在使用newmake时,让我们来解答几个常见问题:

  • 为何切片(slice)、映射(map)和通道(chan)需要专门的make函数来初始化? 这是因为这三种类型在使用前必须初始化。未初始化的切片(slice)、映射(map)和通道(chan)其值为零值,即为nil。其中,nil映射无法添加元素,会导致程序崩溃;nil通道无法进行数据传递,会导致阻塞。而nil切片虽然可以通过append函数扩展,但通常我们需要自定义切片的长度或容量,这就需要使用make
  • 是否可以使用new创建切片(slice)、映射(map)和通道(chan)? 可以,但创建出的将是nil值。例如:
a := *new([]int) b := *new(map[string]int) c := *new(chan int)

这些nil值的类型在实践中无法直接使用。

  • 为什么为nil的切片(slice)调用append也可以正常工作? 对于nil切片,append会自动为其底层数组进行扩容,分配必要的内存空间,然后赋值给原先的nil切片。

最优使用建议

  1. 尽量避免使用new
  2. 在定义和初始化切片(slice)、映射(map)和通道(chan)时,优先考虑使用make

参考资料