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

sle*_*ica 187 go

引入文件奉献许多段落解释之间的区别new()make(),但在实践中,您可以创建本地范围内的物体并返回它们.

你为什么要使用(坦率地说是愚蠢的)配对器?

小智 154

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

&T{...},&someLocalVar,new,make

创建复合文字时也可以进行分配.


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
Run Code Online (Sandbox Code Playgroud)

通过查看以下示例可以看出new和之间的区别make:

p := new(chan int)   // p has type: *chan int
c := make(chan int)  // c has type: chan int
Run Code Online (Sandbox Code Playgroud)

假设Go没有newmake,但它有内置的功能NEW.然后示例代码如下所示:

p := NEW(*chan int)  // * is mandatory
c := NEW(chan int)
Run Code Online (Sandbox Code Playgroud)

* 是强制性的,所以:

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

new(Point)  // Illegal
new(int)    // Illegal
Run Code Online (Sandbox Code Playgroud)

是的,合并newmake成一个单一的内置功能是可能的.但是,单个内置函数很可能会导致新Go程序员比使用两个内置函数更加混乱.

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

  • 您是说在最后两行中写`make(Point)`和`make(int)`吗? (2认同)

Eva*_*haw 153

你可以做的事情make,你不能做任何其他方式:

  • 创建一个频道
  • 创建一个预分配空间的地图
  • 创建一个预先分配空间或使用len!= cap的切片

这有点难以证明new.它更容易的主要是创建指向非复合类型的指针.以下两个功能是等效的.一个更简洁:

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

func newInt2() *int {
    var i int
    return &i
}
Run Code Online (Sandbox Code Playgroud)

  • 确实,'new'不能用于创建频道.但是,在我看来,重点是:如果将'new'和'make'连接到一个内置函数中会发生什么?当然,这样的替代是可能的.既然有可能,问题是:拥有2个内置函数而不仅仅是1个广义内置函数的客观原因是什么. - 您的答案正确地说"新"不能用于创建频道/地图/切片,但它没有提供关于**为什么**Go有'new'和'make'的理由,而不是有1个广义alloc + init函数. (36认同)
  • 是的,这是真的,但数组大小必须是编译时常量,而`make`允许您在运行时指定大小. (16认同)
  • Effective go指出new返回一个归零值,而map分配非归零类型map,slice或channel.请参阅http://golang.org/doc/effective_go.html#allocation_new (12认同)
  • 它们可以合并,甚至可以由Rob Pike提出:https://groups.google.com/d/topic/golang-nuts/kWXYU95XN04/discussion.最终,由于与答案中给出的原因类似的原因,它没有通过. (5认同)

cod*_*boy 24

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

  1. new(T) 分配给定类型 T 的未初始化归零内存,并返回指向该内存的指针,以便它可以使用。清零仅意味着分配的内存将具有给定类型的零值。一些 go 类型的零值是 -
    • 整数 - 0
    • 布尔 - 假
    • 浮动 - 0
    • 细绳 - ””
    • struct - 每个成员的零值

当 new() 需要处理其他三种复合类型 - chan、slice 和 map 时,就会出现问题。这三种类型本质上是特殊的,它们的底层类型不仅仅是另一种类型,而是需要初始化的状态。例如,切片的底层状态由指向内部数组存储的第一个元素的指针、确定可以访问的元素数量的长度以及随着元素数量增长而增加的容量组成。new() 当然无法处理此类类型的分配,因为它们需要额外的初始化步骤,这就是 make() 发挥作用的地方。

  1. make(T, args)为 chan、slice 和 map 类型而设计。它不仅分配chan、slice和map的内部存储类型,还初始化它们的底层状态以使其可供使用。例如,对于切片,它分配内部数组存储,将指针设置为引用该数组中的第一个元素,并设置长度和容量值。


Sin*_*tra 22

make function仅分配和初始化slice,map或chan类型的对象.像new一样,第一个参数是一个类型.但是,它也可以采取第二个论点,即大小.与new不同,make的返回类型与其参数的类型相同,而不是指向它的指针.并且初始化分配的值(不像新的那样设置为零值).原因是slice,map和chan是数据结构.它们需要初始化,否则它们将无法使用.这就是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
Run Code Online (Sandbox Code Playgroud)

  • 在`new([]int)`中,它只是为[]int分配内存,但没有初始化,所以它只返回`nil`;不是指向内存的指针,因为它不可用。`make([]int)` 分配并初始化以使其可用,然后返回其地址。 (2认同)

Bho*_*yar 21

new() 和 make() 之间的区别:

  • new(T) 为 T 类型的新项分配零存储并返回其地址,即 *T 类型的值:它返回指向新分配的 T 类型零值的指针,可供使用;它适用于数组和结构等值类型;它相当于 &T{ }
  • make(T) 返回 T 类型的初始化值;它仅适用于 3 种内置参考类型:切片、贴图和通道。

换句话说,新分配;进行初始化;

在此输入图像描述

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

这很少有用。

在此输入图像描述

p := make([]int, 0)
Run Code Online (Sandbox Code Playgroud)

我们的切片已初始化,但此处指向一个空数组。

这两个语句都不是很有用,以下是:

var v []int = make([]int, 10, 50)
// Or
v := make([]int, 10, 50)
Run Code Online (Sandbox Code Playgroud)

这会分配一个包含 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"
}
Run Code Online (Sandbox Code Playgroud)

渠道:

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)

    }
}
Run Code Online (Sandbox Code Playgroud)


Lor*_*ris 17

  • new(T)-分配内存,并将其设置为在零值类型Ť ..
    ..that是0INT""字符串nil用于被引用类型(切片地图CHAN

    需要注意的是被引用类型只是指针一些底层数据结构,这将不会创建new(T)
    实施例:在以下情况下切片,底层阵列将不会被创建,从而new([]int) 将指针返回到什么

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

    示例:在slice 的情况下,将创建具有指定长度和容量的底层数组
    请记住,与 C 不同,数组是 Go 中的原始类型!


话虽如此:

  • 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)
    }
    
    Run Code Online (Sandbox Code Playgroud)

    运行程序

    -- 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
    
    Run Code Online (Sandbox Code Playgroud)

    进一步阅读:
    https : //golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make


    JRo*_*erC 10

    其他答案中详细介绍了“make”的好处,但“New”比上面未提及的 make 有一个额外的好处:泛型(自 1.18 起)。

    假设您有一组平面(所有字段都是基元)结构,如下所示:

    type SomeStruct struct {
        V1 string `json:"v1"`
        V2 string `json:"v2"`
    }
    
    Run Code Online (Sandbox Code Playgroud)

    并且您想要创建一个映射函数,将 map[string]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
    }
    
    Run Code Online (Sandbox Code Playgroud)

    但是,对于该行myStr := T{},此代码将引发有关无效复合值的错误。myStr := make(T)通过另一个关于没有基础类型的错误将其替换为will。因此,您将替换将myStr := new(T)创建对结构的归零值实例的引用的行。

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

    另一方面,您也可以在此特定示例中使用命名返回类型,但更一般的用法仍然有效。


    M.N*_*air 9

    new(T):它返回一个指向 type 类型T值的指针*T,它分配内存并将其归零。new(T)相当于&T{}

    make(T):它返回一个类型T初始化值,它分配和初始化内存。它用于切片、贴图和通道。


    Lil*_*ard 7

    您需要make()创建通道和贴图(和切片,但也可以从阵列创建).没有替代方法来制作这些,所以你无法make()从你的词典中删除.

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

    • 因此应该避免使用 new 并更喜欢使用 Struct 语法 (2认同)

    jim*_*imt 7

    除了Effective Go中解释的所有内容之外,new(T)和之间的主要区别在于&T{}后者显式执行堆分配.但是应该注意,这是依赖于实现的,因此可能会有所变化.

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

    • '&T {}`显式执行堆分配的声明是AFAIK,不基于规范中的任何内容.实际上我相信逃逸分析已经尽可能地保持这样的*T在堆栈上,其方式与`new(T)`完全相同. (10认同)