GO语言有两种分配原语,内置函数new
和make
。它们的用途不同并且应用于不同的类型,这导致它们很容易让人感到困惑,但其实它们的规则很简单。我们先来谈谈new
。它是一个内置函数,用于分配内存,但与其他一些语言中的同名函数不同,它不初始化内存,只将其归零。也就是说,new(T)
为新的T类型
项分配零存储,并放回其地址,一个*T
类型的值。在Go语言术语中,它返回一个指向新分配的T类型零值的指针。
由于new
返回的内存已经被归零,所以在设计数据结构时,安排每种类型的零值可以在无需进一步初始化的情况下使用是很有帮助的。例如,bytes.Buffer
的文档说明“Buffer
的零值是一个准备好使用的空缓冲区”。同样,sync.Mutex
没有显示的构造函数或init
方法。取而代之,sync.Mutex
的零值被定义为一个未锁定的互斥锁。(若觉得这段话难以理解,可以看下面的译者补充)
这个“零值是有用的”属性是可传递的。考虑下面这个类型声明。
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
因为sync.Mutex
和bytes.Buffer
满足“零值是有用的”的属性,所以SyncedBuffer
也满足这个属性。SyncedBuffer
类型的值在分配或仅声明后也可以立即使用。在下一个代码片段中,p
和v
都可以在没有进一步安排的情况下正确工作。
p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer
接下来是make
,内置函数make(T, args)与new(T)的目的不同。它只创建切片slice、映射map、通道chan,并返回类型T的初始化(非零)值(不是*T
)。区分的原因是,这三种类型在底层代表了必须在使用前初始化的数据结构的引用。例如,切片是一个包含指向数据(在数组内部)的指针、长度和容量的三项描述符,直到这些项被初始化,切片才不是nil
。对于切片、映射和通道,make
初始化内部数据结构并准备好值以供使用。例如,
// 创建int类型切片,初始化10个元素值为0,切片容量为100。
make([]int, 10, 100)
分配一个包含100个int
的数组,然后创建一个长度为10、容量为100的切片结构,该切片指向数组的前10个元素。(在创建切片时,可以省略容量;有关更多信息,请参阅切片部分。)相比之下,new([]int)
返回一个指向新分配的、已清零的切片结构的指针,也就是,一个指向nil
切片值的指针。
下面例子阐明了new和make之间的区别。
var p *[]int = new([]int) // 分配切片结构,*p == nil,很少派上用场。
var v []int = make([]int, 100) // 切片v现在引用一个新的包含100个int的数组。
// 不必要的复杂:
var p *[]int = new([]int)
*p = make([]int, 100, 100)
// 习惯用法:
v := make([]int, 100)
牢记,make
只适用于切片slice、映射map和通道chan,并且不返回指针。要获取显式的指针,可以使用new
进行分配,或者显式地获取变量的地址。
译者补充
我们可以用下面的例子去理解本文的第二段话。
package main
import (
"fmt"
)
func main() {
// 使用new分配一个int类型的变量
num := new(int)
fmt.Println(*num)
// 使用new分配一个bool类型的变量
flag := new(bool)
fmt.Println(*flag)
// 使用new分配一个自定义类型的变量
type Person struct {
Name string
Age int
}
p := new(Person)
fmt.Println(*p)
}
输出:
0
false
{ 0}
在这些例子中,我们使用new
函数分配了不同类型的变量。你可以看到,无论我们分配的是什么类型的变量,new
函数都会返回一个指向该类型零值的指针。对于int
类型,零值是0;对于bool
类型,零值是false
;对于我们自定义的Person
类型,零值是一个所有字段都是其对应类型零值的Person
实例。
package main
import (
"fmt"
"bytes"
"sync"
)
func main() {
// bytes.Buffer的零值是一个可以立即使用的空缓冲区
var buf bytes.Buffer
buf.WriteString("Hello, World!")
fmt.Println(buf.String())
// sync.Mutex的零值是一个未锁定的互斥锁
var m sync.Mutex
m.Lock() // 锁定
m.Unlock() // 解锁
}
输出:
Hello, World!
本文翻译自Effective Go