越来越多的开发者开始使用Go语言开发,这其中不乏有PHPPythonJava的,还有CC++Go的,技术功底可谓是鱼龙混杂、良莠不齐。无论是从小白入门还是直接上手做项目,如果要真正掌握一门编程语言,基本功早晚都要练的。

言归正传,在Go语言中有两个用来内存分配的内置函数:newmake。经历了很多项目,发现make的使用频率要远高于new,实际开发中make几乎是无可代替,之所以new的使用频率没那么高,是因为它有几种可以代替的编写方式。虽然都用于内存分配,但它们还是有一些区别,所以应用场景也各不相同。

NEW

new就是纯用来内存分配的,它返回的是被分配类型的指针,并初始化为该类型的零值

// 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

该函数的第一个参数Type是一个类型,不是一个值,它返回被分配了零值这个类型的指针。零值是类型的默认值,例如:int的零值是0string的零值是"",指针、切片、映射、通道和函数等这些引用类型的零值nil

package main

import (
    "fmt"
)

func main() {
    s := new(string)
    fmt.Println(s) // output: 0xc00012a0b0
    fmt.Println(*s) // output: 
}

可以看出,new(string)分配了一个字符串类型的指针,打印出的0xc00012a0b0为指针地址,其地址上的值为空字符串

package main

import (
    "fmt"
)

type User struct {
    Name string
    Age  int
}

func main() {
    var u *User
    u = new(User)
    u.Name = "alice"
    u.Age = 18
    fmt.Println(u) // output: &{alice 18}
    fmt.Println(*u) // output: {alice 18}
    fmt.Println(&u) // 0xc00005a040
}

上面这个例子,new了一个User类型,返回的是User的指针。因为u是指针类型,如果不分配内存初始化它,它是nil,无法用来赋值并使用,所以通过new来分配内存以后才能赋值使用。我在开头说了new的使用频率并不多,那就是因为它还有一种代替的写法:

package main

type User struct {
    Name string
    Age  int
}

func main() {
    var u *User
    u = &User{}
}

new(User)&User{}有什么区别呢?对于日常开发来说几乎无区别,并且后者更常用和简洁高效,也能在初始化时给元素直接赋值。如果不考虑初始赋值的情况下,前者在性能上更有优势,日常开发可无须过渡关注两者区别。

MAKE

再来说说make函数,make仅用于特定类型的内存分配和初始化,那就是只能用于slicemapchannel这三个特定类型。使用make初始化时,可以指定长度,如果初始化slice,还可以指定切片的初始容量。除此之外,make返回的是类型本身,并不是指针。

package main

import "fmt"

func main() {
    s := make([]int, 4, 16) // size: 4, cap: 16
    fmt.Println(s)          // output [0 0 0 0]

    m := make(map[string]int, 4) // size: 4
    m["k0"] = 1
    fmt.Println(m) // output: map[key0:1]

    ch := make(chan int, 1) // size: 1
    ch <- 10
    fmt.Println(<-ch) // output: 10
}

前面说了,new也可以为指定类型分配内存,那如果用new来为slice map channel分配内存会怎样呢?

package main

import "fmt"

func main() {
    m := new(map[string]string) // m 的类型为 *map[string]string
    fmt.Println(&m)             // 0xc00005a040
    fmt.Println(*m == nil)      // true
    (*m)["k0"] = "v0"           // panic: assignment to entry in nil map
}

这里newmap[string]string分配了内存地址,返回指向该内存地址的指针,但指针指向的地址上的值依然为nil,就是说new并没有为map分配初始结构。

package main

import "fmt"

func main() {
    a := *new([]int)
    fmt.Printf("%v, %t\n", a, a == nil) // output: [], true

    b := *new(map[string]int)
    fmt.Printf("%v, %t\n", b, b == nil) // output: map[], true

    c := *new(chan int)
    fmt.Printf("%v, %t\n", c, c == nil) // output: <nil>, true
}

由此看出,无论是slicemap还是channelnew出来的指针指向的地址上的值都为nil,所以并不能直接使用,这就是make的大有作为。当然在某些条件下,make也是可以替代的,我们可以直接初始化并且还可以赋值:

package main

import "fmt"

func main() {
    s := []int{}
    fmt.Println(s)

    m := map[string]string{}
    fmt.Println(m)
}

需要注意的是chan类型必须通过make初始化,若仅用var声明而不使用make,通道(chan)的默认零值是nil

最后

简而言之,new用来分配内存,返回指针,适用于基本类型结构体指针类型的创建。

make是专用于slicemapchannel这些引用类型的初始化,可以指定长度和容量,返回一个直接可用的结构。