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

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


当前回答

你能用它做的事情用其他方法做不到:

创建通道 创建一个预分配空间的映射 创建一个预先分配空间的切片,或者使用len != cap

要证明新的合理性有点难。它简化的主要事情是创建指向非复合类型的指针。 下面两个函数是等价的。一个更简洁一点:

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

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

其他回答

“make”的好处在其他答案中有很多,但是“New”比上面没有提到的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可以用来实例化编译时未知的类型。

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

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

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

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

科技{…}, &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

new和make的区别可以从下面的例子中看出:

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

假设Go没有new和make,但它有内置函数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

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

考虑到以上几点,new和make保持分离似乎更合适。

你能用它做的事情用其他方法做不到:

创建通道 创建一个预分配空间的映射 创建一个预先分配空间的切片,或者使用len != cap

要证明新的合理性有点难。它简化的主要事情是创建指向非复合类型的指针。 下面两个函数是等价的。一个更简洁一点:

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

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

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

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