Go语言的分配原语:new和make

GO语言有两种分配原语,内置函数newmake。它们的用途不同并且应用于不同的类型,这导致它们很容易让人感到困惑,但其实它们的规则很简单。我们先来谈谈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.Mutexbytes.Buffer满足“零值是有用的”的属性,所以SyncedBuffer也满足这个属性。SyncedBuffer类型的值在分配或仅声明后也可以立即使用。在下一个代码片段中,pv都可以在没有进一步安排的情况下正确工作。

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值