介绍文档用很多段落来解释 new()
和 make()
之间的区别,但实际上,您可以在本地范围内创建对象并返回它们。
为什么要使用这对分配器?
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
通过查看以下示例可以看出 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
合并到一个内置函数中。然而,一个单一的内置函数可能会比拥有两个内置函数在新的 Go 程序员中导致更多的混乱。
考虑到以上所有几点,new
和 make
保持分开似乎更合适。
您可以用 make
做的事情是其他任何方式都无法做到的:
创建频道
创建一个预先分配空间的地图
使用预先分配的空间或使用 len != cap 创建切片
证明 new
的合理性有点困难。它使更容易的主要事情是创建指向非复合类型的指针。下面的两个函数是等价的。一个更简洁一点:
func newInt1() *int { return new(int) }
func newInt2() *int {
var i int
return &i
}
m := map[string]int{}
而不是 m := make(map[string]int)
呢?也不需要预先分配大小。
make
函数仅分配和初始化 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
new([]int)
中,它只是为 []int 分配内存,但没有初始化,所以它只返回 nil
;不是指向内存的指针,因为它不可用。 make([]int)
分配和初始化以便它可用,然后返回它的地址。
new(T) - 分配内存,并将其设置为 T 类型的零值。..int 为 0,字符串为 "",引用类型 (slice, map, chan) 为 nil 请注意,引用类型只是指向一些底层数据结构的指针,它不会由 new(T) 创建示例:在 slice 的情况下,不会创建底层数组,因此 new([]int) 返回一个指向空的指针
make(T) - 为引用的数据类型(slice、map、chan)分配内存,并初始化它们的底层数据结构示例:在切片的情况下,底层数组将以指定的长度和容量创建请记住,不像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("指针值: %p\n\n", *aPtr) fmt.Println("-- COMPOSITE LITERAL --") b := []int{} bPtr := &b fmt.Println("指针 == nil :", *bPtr == nil) fmt.Printf("指针值: %p\n\n", *bPtr) fmt.Println( "-- NEW --") cPtr := new([]int) fmt.Println("pointer == nil :", *cPtr == nil) fmt.Printf("指针值: %p\n\n" , *cPtr) fmt.Println("-- VAR (未初始化) --") var d []int dPtr := &d fmt.Println("pointer == nil :", *dPtr == nil) fmt.Printf ("pointer value: %p\n", *dPtr) } 运行程序 -- MAKE -- pointer == nil : false 指针值: 0x118eff0 # 指向底层数组的地址 -- COMPOSITE LITERAL -- pointer == nil : false指针值:0x118eff0 # 底层数组的地址 -- 新 -- 指针 == nil :真 指针值:0x0 -- VAR(未初始化) -- 指针 == nil :真 指针值:0x0 进一步广告:https://golang.org/doc/effective_go.html#allocation_new https://golang.org/doc/effective_go.html#allocation_make
已经有很多好的答案,但让我解释一下 new() 和 make() 作为单独分配器的必要性。
new(T) 分配给定类型 T 的未初始化的零内存,并返回一个指向该内存的指针,以便它可以使用。清零仅意味着分配的内存将具有给定类型的零值。一些go类型的零值是 -int - 0 bool - false float - 0 string - "" struct - 每个成员的零值
当 new() 需要处理其他三种复合类型——chan、slice 和 map 时,就会出现问题。这三种类型在本质上是特殊的,它们的底层类型不仅仅是另一种类型,而是需要初始化的状态。例如,切片的底层状态由指向内部数组存储的第一个元素的指针、确定可以访问的元素数量的长度以及随着元素数量的增长而增加的容量组成。 new() 当然不能处理这些类型的分配,因为它们需要额外的初始化步骤,这就是 make() 发挥作用的地方。
make(T, args) 是专门为 chan、slice 和 map 类型制作的。它不仅分配了 chan、slice 和 map 的内部存储类型,还初始化了它们的底层状态以使它们准备好使用。例如,对于切片,它分配内部数组存储,设置指针以引用该数组中的第一个元素并设置长度和容量值。
new(T)
:它返回一个 指针,指向类型 T
类型为 *T
的值,它分配并清零内存。 new(T)
等同于 &T{}
。
make(T)
:它返回一个T
类型的初始化值,它分配和初始化内存。它用于切片、地图和通道。
您需要 make()
来创建通道和映射(以及切片,但也可以从数组创建)。没有其他方法可以制作这些,因此您无法从词典中删除 make()
。
至于 new()
,当您可以使用 struct 语法时,我不知道为什么您需要它。不过,它确实具有独特的语义含义,即“创建并返回一个所有字段都初始化为零值的结构”,这很有用。
除了 Effective Go 中解释的所有内容外,new(T)
和 &T{}
之间的主要区别在于后者显式执行堆分配。但是应该注意,这取决于实现,因此可能会发生变化。
比较 make
和 new
没有什么意义,因为两者执行完全不同的功能。但这在链接的文章中有详细说明。
&T{}
显式执行堆分配的说法是 AFAIK 不基于规范中的任何内容。实际上,我相信逃逸分析已经尽可能以与 new(T)
完全相同的方式将此类 *T 保留在堆栈中。
new() 和 make() 的区别:
new(T) 为 T 类型的新项目分配零存储并返回其地址,*T 类型的值:它返回指向新分配的 T 类型零值的指针,准备使用;它适用于数组和结构等值类型;它相当于 &T{ }
make(T) 返回一个 T 类型的初始化值;它仅适用于 3 种内置引用类型:切片、映射和通道。
换句话说,新分配;进行初始化;
https://i.stack.imgur.com/PHIBg.jpg
var p *[]int = new([]int)
or
// *p == nil; with len and cap 0
p := new([]int)
这很少有用。
https://i.stack.imgur.com/MUiI3.jpg
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)
}
}
“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
可用于实例化编译时未知的类型。
另一方面,您也可以在此特定示例中使用命名返回类型,但更一般的用法仍然存在。
int
的实例。make(Point)
和make(int)
吗?