Go 语言中 make 和new 的区别

Go 语言中 make 和new 的区别

声明一个变量

csharp 复制代码
var a int
var b string

这里我们使用 var 关键字,声明两个变量,然后就可以在程序中使用。当我们不指定变量的默认值的时候呢,这些变量的默认值是它所属类型的零值。比如上面的 int 型它的零值为 0,string 的零值为 "",引用类型的零值为 nil。

接下来尝试一下定义引用类型。

go 复制代码
package main

import "fmt"

func main() {
    var a *int
    *a = 10
    fmt.Println(*a)
}

执行一下这个程序,它会发生 panic,原因是:

go 复制代码
panic: runtime error: invalid memory address or nil pointer dereference

从这我们可以看出,如果是一个引用类型,我们不仅要声明它,还要为它分配内存空间,否则我们赋值的 10 就无处安放。值类型的声明不需要我们分配内存空间,因为已经默认给我们分配好啦。

new 关键字

我们使用 new 关键字来改写一下上面的 demo

go 复制代码
package main

import "fmt"

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

我们可以运行成功,并且成功打印出 10。下面贴出 new 内置函数的注释。

dart 复制代码
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly\
// allocated zero value of that type.
func new(Type) *Type

简单翻译:此内置函数用于分配内存,第一个参数接收一个类型而不是一个值,函数返回一个指向该类型内存地址的指针,同时把分配的内存置为该类型的零值。

内置函数 new 会在编译期的,Go 语言编译器的中间代码具有静态单赋值(SSA)的特性,代码生成阶段经过 callnew 函数的处理,如果请求创建的类型大小是 0,那么就会返回一个表示空指针的 zerobase 变量,在遇到其他情况时会将关键字转换成 newobject

scss 复制代码
func callnew(t *types.Type) *Node {
    if t.NotInHeap() {
        yyerror("%v is go:notinheap; heap allocation disallowed", t)
    }
    dowidth(t)

    if t.Size() == 0 {
        z := newname(Runtimepkg.Lookup("zerobase"))
        z.SetClass(PEXTERN)
        z.Type = t
        return typecheck(nod(OADDR, z, nil), ctxExpr)
    }

    fn := syslook("newobject")
    fn = substArgTypes(fn, t)
    v := mkcall1(fn, types.NewPtr(t), nil, typename(t))
    v.SetNonNil(true)
    return v
}

需要提到的是,哪怕当前变量是使用 var 进行初始化,在这一阶段也可能会被转换成 newobject 的函数调用并在堆上申请内存:

go 复制代码
func walkstmt(n *Node) *Node {
    switch n.Op {
    case ODCL:
        v := n.Left
        if v.Class() == PAUTOHEAP {
            if prealloc[v] == nil {
                prealloc[v] = callnew(v.Type)
            }
            nn := nod(OAS, v.Name.Param.Heapaddr, prealloc[v])
            nn.SetColas(true)
            nn = typecheck(nn, ctxStmt)
            return walkstmt(nn)
        }
    case ONEW:
        if n.Esc == EscNone {
            r := temp(n.Type.Elem())
            r = nod(OAS, r, nil)
            r = typecheck(r, ctxStmt)
            init.Append(r)
            r = nod(OADDR, r.Left, nil)
            r = typecheck(r, ctxExpr)
            n = r
        } else {
            n = callnew(n.Type.Elem())
        }
    }
}

newobject 函数的工作就是获取传入类型的大小并调用 mallocgc 在堆上申请一片大小合适的内存空间并返回指向这片内存空间的指针:

arduino 复制代码
func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

make 关键字

make 也是用于分配内存,与 new 不同的是,它一般只用于chan,map,slice 的初始化,并且直接返回这三种类型本身。以下是 make 内置函数的注释。

scss 复制代码
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//  Slice: The size specifies the length. The capacity of the slice is
//  equal to its length. A second integer argument may be provided to
//  specify a different capacity; it must be no smaller than the
//  length. For example, make([]int, 0, 10) allocates an underlying array
//  of size 10 and returns a slice of length 0 and capacity 10 that is
//  backed by this underlying array.
//  Map: An empty map is allocated with enough space to hold the
//  specified number of elements. The size may be omitted, in which case
//  a small starting size is allocated.
//  Channel: The channel's buffer is initialized with the specified
//  buffer capacity. If zero, or the size is omitted, the channel is
//  unbuffered.
func make(t Type, size ...IntegerType) Type

从声明也能看出,返回的还是该类型。

go 复制代码
new的语法是:func new(Type) *Type;
make的语法为:func make(t Type, size ...IntegerType) Type

两者异同

  • new 和 make 都是用于内存的分配。
  • make 只用于 chan,map,slice 的初始化。
  • new 用于给类型分配内存空间,并且置零。
  • make 返回类型本身,new 返回指向类型的指针。

参考资料

相关推荐
Estar.Lee3 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
2401_857610034 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_5 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞5 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货5 小时前
Rust 的简介
开发语言·后端·rust
monkey_meng5 小时前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
Estar.Lee6 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
新知图书6 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放7 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang7 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net