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

huangapple go评论63阅读模式
英文:

Why would I make() or new()?

问题

为什么要使用new()make()这对分配器?

英文:

The introduction documents dedicate many paragraphs to explaining the difference between new() and make(), but in practice, you can create objects within local scope and return them.

Why would you use the pair of allocators?

答案1

得分: 289

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

<code>&T{...}</code>, <code>&someLocalVar</code>, <code>new</code>, <code>make</code>

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


<code>new</code>可以用于分配整数等值,<code>&int</code>是非法的:

new(Point)
&amp;Point{}      // 正确
&amp;Point{2, 3}  // 结合分配和初始化

new(int)
&amp;int          // 非法

// 可行,但比new(int)写起来不方便
var i int
&amp;i

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

p := new(chan int)   // p的类型为:*chan int
c := make(chan int)  // c的类型为:chan int

假设Go没有<code>new</code>和<code>make</code>,但有内置函数<code>NEW</code>。那么示例代码将如下所示:

p := NEW(*chan int)  // *是必需的
c := NEW(chan int)

<code>*</code> 是必需的,所以:

new(int)        --&gt;  NEW(*int)
new(Point)      --&gt;  NEW(*Point)
new(chan int)   --&gt;  NEW(*chan int)
make([]int, 10) --&gt;  NEW([]int, 10)

make(Point)  // 非法
make(int)    // 非法

是的,将<code>new</code>和<code>make</code>合并为一个内置函数是可能的。然而,一个内置函数可能会在新的Go程序员中引起更多的困惑,而不是拥有两个内置函数。

考虑到上述所有观点,更合适的是将<code>new</code>和<code>make</code>保持分开。

英文:

Go has multiple ways of memory allocation and value initialization:

<code>&T{...}</code>, <code>&someLocalVar</code>, <code>new</code>, <code>make</code>

Allocation can also happen when creating composite literals.


<code>new</code> can be used to allocate values such as integers, <code>&int</code> is illegal:

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

new(int)
&amp;int          // Illegal

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

The difference between <code>new</code> and <code>make</code> can be seen by looking at the following example:

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

Suppose Go does not have <code>new</code> and <code>make</code>, but it has the built-in function <code>NEW</code>. Then the example code would look like this:

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

The <code>*</code> would be mandatory, so:

new(int)        --&gt;  NEW(*int)
new(Point)      --&gt;  NEW(*Point)
new(chan int)   --&gt;  NEW(*chan int)
make([]int, 10) --&gt;  NEW([]int, 10)

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

Yes, merging <code>new</code> and <code>make</code> into a single built-in function is possible. However, it is probable that a single built-in function would lead to more confusion among new Go programmers than having two built-in functions.

Considering all of the above points, it appears more appropriate for <code>new</code> and <code>make</code> to remain separate.

答案2

得分: 223

使用make可以做到其他方式无法实现的事情:

  • 创建一个通道
  • 创建一个预分配空间的映射
  • 创建一个预分配空间或长度不等于容量的切片

对于new来说,稍微有点难以证明其必要性。它主要简化了创建非复合类型的指针的过程。
下面的两个函数是等价的,只是第二个函数更加简洁:

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

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

Things you can do with make that you can't do any other way:

  • Create a channel
  • Create a map with space preallocated
  • Create a slice with space preallocated or with len != cap

It's a little harder to justify new. The main thing it makes easier is creating pointers to non-composite types.
The two functions below are equivalent. One's just a little more concise:

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

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

答案3

得分: 46

make函数只能分配和初始化类型为slice、map或chan的对象。与new类似,第一个参数是类型。但是,它还可以接受第二个参数,即大小。与new不同,make的返回类型与其参数的类型相同,而不是指向它的指针。并且分配的值被初始化(不像new中的零值)。**原因是slice、map和chan是数据结构。它们需要被初始化,否则它们将无法使用。**这就是为什么new()make()需要不同的原因。

以下是来自《Effective Go》的示例,它们非常清楚:

p *[]int = new([]int) // *p = nil,这使得p无用
v []int = make([]int, 100) // 创建一个具有指向数组的指针、长度字段和容量字段的v结构。因此,v可以立即使用
英文:

make function allocates and initializes an object of type slice, map, or chan only. Like new, the first argument is a type. But, it can also take a second argument, the size. Unlike new, make’s return type is the same as the type of its argument, not a pointer to it. And the allocated value is initialized (not set to zero value like in new). The reason is that slice, map and chan are data structures. They need to be initialized, otherwise they won't be usable. This is the reason new() and make() need to be different.

The following examples from Effective Go make it very clear:

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

答案4

得分: 34

  • new(T) - 分配内存,并将其设置为类型 T零值。对于 int 来说是 0,对于 string 来说是 "",对于引用类型(slicemapchan)来说是 nil

    需要注意的是,引用类型只是指向某些底层数据结构的指针,这些数据结构不会被 new(T) 创建。

    例如:对于 slice 来说,底层数组不会被创建,因此 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)
}

运行程序

-- MAKE --
pointer == nil : false
pointer value: 0x118eff0  # 底层数组的地址

-- COMPOSITE LITERAL --
pointer == nil : false
pointer value: 0x118eff0  # 底层数组的地址

-- NEW --
pointer == nil : true
pointer value: 0x0

-- VAR (not initialized) --
pointer == nil : true
pointer value: 0x0

进一步阅读:

英文:
  • new(T) - Allocates memory, and sets it to the zero value for type T..<br/>
    ..that is 0 for int, &quot;&quot; for string and nil for referenced types (slice, map, chan)

    Note that referenced types are just pointers to some underlying data structures, which won't be created by new(T)<br/>
    Example: in case of slice, the underlying array won't be created, thus new([]int)
    returns a pointer to nothing<br/><br/>

  • make(T) - Allocates memory for referenced data types (slice, map, chan), plus initializes their underlying data structures

    Example: in case of slice, the underlying array will be created with the specified length and capacity<br/>
    Bear in mind that, unlike C, an array is a primitive type in Go!


That being said:<br/>
<li> make(T) behaves like composite-literal syntax
<li> new(T) behaves like var (when the variable is not initialized)

<!-- language: go -->

func main() {
    fmt.Println(&quot;-- MAKE --&quot;)
    a := make([]int, 0)
    aPtr := &amp;a
    fmt.Println(&quot;pointer == nil :&quot;, *aPtr == nil)
    fmt.Printf(&quot;pointer value: %p\n\n&quot;, *aPtr)

    fmt.Println(&quot;-- COMPOSITE LITERAL --&quot;)
    b := []int{}
    bPtr := &amp;b
    fmt.Println(&quot;pointer == nil :&quot;, *bPtr == nil)
    fmt.Printf(&quot;pointer value: %p\n\n&quot;, *bPtr)

    fmt.Println(&quot;-- NEW --&quot;)
    cPtr := new([]int)
    fmt.Println(&quot;pointer == nil :&quot;, *cPtr == nil)
    fmt.Printf(&quot;pointer value: %p\n\n&quot;, *cPtr)

    fmt.Println(&quot;-- VAR (not initialized) --&quot;)
    var d []int
    dPtr := &amp;d
    fmt.Println(&quot;pointer == nil :&quot;, *dPtr == nil)
    fmt.Printf(&quot;pointer value: %p\n&quot;, *dPtr)
}

Run the program

<!-- language: sh -->

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

Further reading:<br/>
https://golang.org/doc/effective_go.html#allocation_new
https://golang.org/doc/effective_go.html#allocation_make

答案5

得分: 21

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

  1. new(T) 分配给定类型T的未初始化的零值内存,并返回指向该内存的指针,以便可以立即使用。零值意味着分配的内存将具有给定类型的零值。一些go类型的零值是:
    • int - 0
    • bool - false
    • float - 0
    • string - ""
    • struct - 每个成员的零值

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

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

There are already a lot of good answers but let me explain the need for new() and make() as separate allocators.

  1. new(T) allocates uninitialized zeroed memory of the given type T and returns a pointer to that memory so that it is ready to use. Zeroed out just means that the allocated memory will have zero value of given type. Zero values of some go types are -
    • int - 0
    • bool - false
    • float - 0
    • string - ""
    • struct - Zero value of each member

Problem with new() arises when it needs to handle three other composite types - chan, slice and map.
These three types are special in essence that their underlying type is not just an another type but rather a state that needs to be initialized. For example , the underlying state of a slice consists of a pointer to the first element of internal array storage, a length that determines number of elements that can be accessed and a capacity that increases as the number of elements grow. new() certainly cannot handle allocation of such types due to their need for extra initialization step, that is where make() come into play.

  1. make(T, args) is specially made for chan, slice and map types. It not only allocates the internal storage type of the chan, slice and map but also initializes their underlying state to make them ready to use. For example, for a slice it allocates the internal array storage, set the pointer to refer to first element in that array and set the length and capacity values.

答案6

得分: 20

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

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

英文:

new(T): it returns a pointer to type T a value of type *T, it allocates and zeroes the memory. new(T) is equivalent to &amp;T{}.

make(T): it returns an initialized value of type T, It allocates and initializes the memory. Its used for slices, map and channels.

答案7

得分: 18

new()和make()的区别:

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

换句话说,new分配内存;make初始化。

var p *[]int = new([]int)
或
// *p == nil; 长度和容量为0
p := new([]int)

这只在极少数情况下有用。

p := make([]int, 0)

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

这两个语句并不是很有用,下面的语句才是:

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

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

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

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

package main
type Foo map[string]string
type Bar struct {
		 s string
		 i int
}
func main() {
		 // 正确:
		 y := new(Bar)
		 (*y).s = "hello"
		 (*y).i = 1

		 // 不正确:
		 z := make(Bar) // 编译错误:无法创建类型Bar
		 z.s = "hello"
		 z.i = 1

		 // 正确:
		 x := make(Foo)
		 x["x"] = "goodbye"
		 x["y"] = "world"

		 // 不正确:
		 u := new(Foo)
		 (*u)["x"] = "goodbye" // !!panic!!: 运行时错误:对nil映射的条目赋值
		 (*u)["y"] = "world"
}

通道:

func main() {
	// 正确:
	ch := make(chan string)
	go sendData(ch)
	go getData(ch)
	time.Sleep(1e9)

	// 不正确:
	ch := new(chan string)
	go sendData(ch) // 无法将ch(类型为*chan string的变量)作为参数传递给sendData中的chan string值
	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)

	}
}
英文:

Difference between new() and make():

  • new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T: it returns a pointer to a newly allocated zero value of type T, ready for use; it applies to value types like arrays and structs; it is
    equivalent to &T{ }
  • make(T) returns an initialized value of type T; it applies only to the 3 built-in reference types: slices, maps and channels.

In other words, new allocates; make initializes;

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

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

which is only rarely useful.

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

p := make([]int, 0)

our slice is initialized, but here points to an empty array.

Both these statements aren't very useful, the following is:

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

This allocates an array of 50 ints and then creates a slice v with length 10 and capacity 50 pointing to the first 10 elements of the array.

Find out some rules for make() and new():

  • For slices, maps and channels: use make
  • For arrays, structs and all value types: use new

package main
type Foo map[string]string
type Bar struct {
		 s string
		 i int
}
func main() {
		 // OK:
		 y := new(Bar)
		 (*y).s = &quot;hello&quot;
		 (*y).i = 1

		 // NOT OK:
		 z := make(Bar) // compile error: cannot make type Bar
		 z.s = &quot;hello&quot;
		 z.i = 1

		 // OK:
		 x := make(Foo)
		 x[&quot;x&quot;] = &quot;goodbye&quot;
		 x[&quot;y&quot;] = &quot;world&quot;

		 // NOT OK:
		 u := new(Foo)
		 (*u)[&quot;x&quot;] = &quot;goodbye&quot; // !!panic!!: runtime error: 
                   // assignment to entry in nil map
		 (*u)[&quot;y&quot;] = &quot;world&quot;
}

Channel:

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 &lt;- &quot;Washington&quot;
	ch &lt;- &quot;Tripoli&quot;
	ch &lt;- &quot;London&quot;
	ch &lt;- &quot;Beijing&quot;
	ch &lt;- &quot;Tokio&quot;
}

func getData(ch chan string) {
	var input string
	for {
		input = &lt;-ch
		fmt.Printf(&quot;%s &quot;, input)

	}
}

答案8

得分: 12

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

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

英文:

You need make() to create channels and maps (and slices, but those can be created from arrays too). There's no alternative way to make those, so you can't remove make() from your lexicon.

As for new(), I don't know of any reason offhand why you need it when you can use struct syntax. It does have a unique semantic meaning though, which is "create and return a struct with all fields initialized to their zero value", which can be useful.

答案9

得分: 8

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

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

英文:

Apart from everything explained in Effective Go, The main difference between new(T) and &amp;T{} is that the latter explicitly performs a heap allocation. However it should be noted that this is implementation dependent and thus may be subject to change.

Comparing make to new makes little sense as the two perform entirely different functions. But this is explained in detail in the linked article.

答案10

得分: 8

"make"的好处在其他答案中已经详细介绍了,但是"New"相比make还有一个额外的优点:泛型(从1.18版本开始)。

假设你有一组扁平的(所有字段都是基本类型)结构体,如下所示:

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

并且你想创建一个将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
}

但是,这段代码会抛出一个关于myStr := T{}这行的错误,错误信息是关于无效的复合值。将其替换为myStr := make(T)会抛出另一个关于没有底层类型的错误。所以,你需要将这行替换为myStr := new(T),它将创建一个指向结构体的零值实例的引用。

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

顺便说一下,在这个特定的例子中,你也可以使用命名返回类型,但更一般的用法仍然适用。

英文:

The benefits of "make" are heavily covered in other answers, but "New" has an added bonus over make not mentioned above: generics (as of 1.18).

Let's say you have a set of flat (all fields are primitives) structs, like the following:

type SomeStruct struct {
	V1 string `json:&quot;v1&quot;`
	V2 string `json:&quot;v2&quot;`
}

and you want to create a mapping function that turns a map[string]string into any struct. Then you could write:

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
}

but, this code will throw an error, with regards to the line myStr := T{}, about an invalid composite value. Replacing it with myStr := make(T) will through another error about no underlying type. So, you'll to replace the line with myStr := new(T) which will create a reference to a zeroed value instance of the struct.

As can be seen, when dealing with generics, new can be used to instantiate a type that is unknown at compile time.

As a side, you could have also used named return types in this specific example, but the more general usage still stands.

huangapple
  • 本文由 发表于 2012年2月17日 07:42:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/9320862.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定