介绍文档用了很多段落来解释new()和make()之间的区别,但实际上,您可以在局部范围内创建对象并返回它们。
为什么要使用这对分配器?
介绍文档用了很多段落来解释new()和make()之间的区别,但实际上,您可以在局部范围内创建对象并返回它们。
为什么要使用这对分配器?
当前回答
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)
这很少有用。
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()来创建通道和映射(以及切片,但这些也可以从数组创建)。没有其他方法来创建这些,所以不能从词典中删除make()。
至于new(),当可以使用结构语法时,我不知道为什么还需要它。但它确实有一个独特的语义含义,即“创建并返回一个将所有字段初始化为零值的结构体”,这很有用。
除了在Effective Go中解释的所有内容外,new(T)和&T{}之间的主要区别是后者显式执行堆分配。但是,应该注意的是,这取决于实现,因此可能会发生变化。
比较make和new没有什么意义,因为两者执行完全不同的功能。但这在链接的文章中有详细的解释。
“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可以用来实例化编译时未知的类型。
另一方面,在这个特定的示例中还可以使用命名返回类型,但更普遍的用法仍然有效。
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
}