为什么我要make()或new()?

介绍文档用了很多段落来解释new()make()之间的区别,但实际上,你可以在局部作用域内创建对象并返回它们。

为什么要使用这对分配器?

151980 次浏览

你需要make()来创建通道和映射(以及切片,但这些也可以从数组创建)。没有其他方法来创建它们,所以不能从词典中删除make()

至于new(),当你可以使用结构语法时,我不知道为什么要立即需要它。但它确实有一个独特的语义含义,即“创建并返回一个将所有字段初始化为零值的结构体”,这很有用。

除了在有效的去中解释的一切之外,new(T)&T{}之间的主要区别是后者显式执行堆分配。但是,应该注意的是,这取决于实现,因此可能会发生变化。

比较makenew没有什么意义,因为两者执行完全不同的功能。但这在链接的文章中有详细的解释。

你可以用make做一些其他方法做不到的事情:

  • 创建通道
  • 创建一个预分配空间的映射
  • 创建一个预先分配空间的切片,或者使用len != cap
要证明new的正确性有点难。它简化的主要事情是创建指向非复合类型的指针。 下面两个函数是等价的。一个更简洁一点:

func newInt1() *int { return new(int) }


func newInt2() *int {
var i int
return &i
}

Go有多种内存分配和值初始化的方式:

__abc0, __abc1, __abc2, __abc3

在创建复合字面量时也可以进行分配。


new可用于分配整数等值,&int是非法的:

new(Point)
&Point{}      // OK
&Point{2, 3}  // Combines allocation and initialization


new(int)
&int          // Illegal


// Works, but it is less convenient to write than new(int)
var i int
&i

newmake之间的区别可以通过下面的例子看出:

p := new(chan int)   // p has type: *chan int
c := make(chan int)  // c has type: chan int

假设Go没有newmake,但它有内置函数NEW。然后示例代码看起来像这样:

p := NEW(*chan int)  // * is mandatory
c := NEW(chan int)

* 将是强制性的,因此:

new(int)        -->  NEW(*int)
new(Point)      -->  NEW(*Point)
new(chan int)   -->  NEW(*chan int)
make([]int, 10) -->  NEW([]int, 10)


make(Point)  // Illegal
make(int)    // Illegal

是的,可以将newmake合并为一个内置函数。然而,一个内置函数可能比两个内置函数更容易让新程序员感到困惑。

考虑到以上所有要点,newmake保持独立似乎更合适。

make函数仅分配和初始化slice、map或chan类型的对象。像new一样,第一个参数是一个类型。但是,它还可以有第二个参数,大小。与new不同,make的返回类型与其实参的类型相同,而不是指向它的指针。并初始化分配的值(不像new中那样设置为零值)。这就是new()和make()需要不同的原因。

下面是Effective Go的例子:

p *[]int = new([]int) // *p = nil, which makes p useless
v []int = make([]int, 100) // creates v structure that has pointer to an array, length field, and capacity field. So, v is immediately usable

new(T):它返回一个指向类型T的指针类型为*T的值,它分配并归零内存。__ABC0等价于&T{}

make(T):返回T类型的初始化值,用于分配和初始化内存。它用于切片,映射和通道。

  • new(T) -分配内存,并将其设置为T类型的零值 ..
    ..即0用于int""用于字符串nil用于引用类型(地图)

    注意,引用的类型只是指向一些底层数据结构的指针,不会被创建 by new(T)
    例如:在的情况下,底层的数组不会被创建,因此new([]int) 返回一个不指向任何内容的指针

  • make(T) -为引用的数据类型(地图)分配内存,加上初始化它们的底层数据结构

    示例:在的情况下,底层的数组将创建具有指定长度和容量的
    请记住,与C语言不同,数组在Go中是一种基本类型!李< / p > < / >


也就是说:

  • make(T)的行为类似于复合文字语法
  • new(T)的行为类似var(当变量未初始化时)

    func main() {
    fmt.Println("-- MAKE --")
    a := make([]int, 0)
    aPtr := &a
    fmt.Println("pointer == nil :", *aPtr == nil)
    fmt.Printf("pointer value: %p\n\n", *aPtr)
    
    
    fmt.Println("-- COMPOSITE LITERAL --")
    b := []int{}
    bPtr := &b
    fmt.Println("pointer == nil :", *bPtr == nil)
    fmt.Printf("pointer value: %p\n\n", *bPtr)
    
    
    fmt.Println("-- NEW --")
    cPtr := new([]int)
    fmt.Println("pointer == nil :", *cPtr == nil)
    fmt.Printf("pointer value: %p\n\n", *cPtr)
    
    
    fmt.Println("-- VAR (not initialized) --")
    var d []int
    dPtr := &d
    fmt.Println("pointer == nil :", *dPtr == nil)
    fmt.Printf("pointer value: %p\n", *dPtr)
    }
    

    运行程序

    -- MAKE --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    
    -- COMPOSITE LITERAL --
    pointer == nil : false
    pointer value: 0x118eff0  # address to underlying array
    
    
    -- NEW --
    pointer == nil : true
    pointer value: 0x0
    
    
    -- VAR (not initialized) --
    pointer == nil : true
    pointer value: 0x0
    
    < p >进一步阅读:
    https://golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make < / p >
  • new()和make()的区别:

    • new(T)为T类型的新项分配零存储,并返回它的地址,一个*T类型的值:它返回一个指向新分配的T类型的零值的指针,准备使用;它适用于数组和结构等值类型;它是 等价于&T{}
    • make(T)返回一个初始化的T类型值;它只适用于3种内置的引用类型:切片、映射和通道。

    换句话说,新的分配;使初始化;

    enter image description here

    var p *[]int = new([]int)
    or
    // *p == nil; with len and cap 0
    p := new([]int)
    

    这很少有用。

    enter image description here

    p := make([]int, 0)
    

    我们的切片已经初始化,但这里指向一个空数组。

    这两种说法都不是很有用,下面是:

    var v []int = make([]int, 10, 50)
    // Or
    v := make([]int, 10, 50)
    

    这将分配一个50个整型数组,然后创建一个长度为10,容量为50的切片v,指向数组的前10个元素。

    找出make()和new()的一些规则:

    • 对于切片、映射和通道:使用make
    • 对于数组、结构和所有值类型:使用new

    package main
    type Foo map[string]string
    type Bar struct {
    s string
    i int
    }
    func main() {
    // OK:
    y := new(Bar)
    (*y).s = "hello"
    (*y).i = 1
    
    
    // NOT OK:
    z := make(Bar) // compile error: cannot make type Bar
    z.s = "hello"
    z.i = 1
    
    
    // OK:
    x := make(Foo)
    x["x"] = "goodbye"
    x["y"] = "world"
    
    
    // NOT OK:
    u := new(Foo)
    (*u)["x"] = "goodbye" // !!panic!!: runtime error:
    // assignment to entry in nil map
    (*u)["y"] = "world"
    }
    

    渠道:

    func main() {
    // OK:
    ch := make(chan string)
    go sendData(ch)
    go getData(ch)
    time.Sleep(1e9)
    
    
    // NOT OK:
    ch := new(chan string)
    go sendData(ch) // cannot use ch (variable of type *chan string)
    // as chan string value in argument to sendData
    go getData(ch)
    time.Sleep(1e9)
    }
    
    
    func sendData(ch chan string) {
    ch <- "Washington"
    ch <- "Tripoli"
    ch <- "London"
    ch <- "Beijing"
    ch <- "Tokio"
    }
    
    
    func getData(ch chan string) {
    var input string
    for {
    input = <-ch
    fmt.Printf("%s ", input)
    
    
    }
    }
    

    已经有很多很好的答案,但是让我解释一下new()和make()作为单独的分配器的必要性。

    1. new(T)为给定类型T分配未初始化的零内存,并向该内存返回指针,以便它可以使用。归零仅仅意味着分配的内存中给定类型的值为零。某些go类型的零值为-
      • Int - 0
      • Bool - false
      • Float - 0
      • 字符串- ""
      • struct -每个成员的零值
    当new()需要处理其他三种复合类型——chan、slice和map时,就会出现问题。 这三种类型在本质上是特殊的,它们的底层类型不仅仅是另一种类型,而是需要初始化的状态。例如,片的底层状态包括指向内部数组存储的第一个元素的指针、决定可访问元素数量的长度以及随着元素数量的增长而增加的容量。New()当然不能处理这些类型的分配,因为它们需要额外的初始化步骤,这就是make()发挥作用的地方
    1. make(T, args)是特别,用于chan、slice和map类型。它不仅分配chan、slice和map的内部存储类型,还分配初始化它们的底层状态以使它们可以使用。例如,对于片,它分配内部数组存储,设置指针指向该数组中的第一个元素,并设置长度和容量值。

    “make"在其他答案中被大量覆盖,但“新”;与上面没有提到的make相比,有一个额外的好处:泛型(从1.18开始)。

    假设你有一组平面(所有字段都是原语)结构体,如下所示:

    type SomeStruct struct {
    V1 string `json:"v1"`
    V2 string `json:"v2"`
    }
    

    你想要创建一个映射函数,将一个map[string]字符串转换为任何结构体。然后你可以这样写:

    func GetStructFromMap[T any](values map[string]string) (T, error) {
    myStr := T{}
    bytes, err := json.Marshal(values)
    if err != nil {
    return *myStr, err
    }
    
    
    if err := json.Unmarshal(bytes, str); err != nil {
    return *myStr, err
    }
    
    
    return *myStr, nil
    }
    

    但是,这段代码将抛出一个关于myStr := T{}行的错误,关于无效的复合值。用myStr := make(T)替换它将导致另一个关于没有底层类型的错误。因此,您将用myStr := new(T)替换该行,这将创建一个对该结构的零值实例的引用。

    可以看到,在处理泛型时,new可用于实例化编译时未知的类型。

    另一方面,在这个特定的示例中还可以使用命名返回类型,但更普遍的用法仍然有效。