Go 语言提供了 new () 和 make () ,该如何选择?
这是有关 Go 内置函数 make
和 new
的帖子。
正如 Rob Pike 在今年 Gophercon 上指出的那样,Go 有许多初始化变量的方法。其中之一就是能够获取 struct literal 地址的能力,这将导致实现相同操作的多种方法。
s := &SomeStruct{}
v := SomeStruct{}
s := &v // 相同
s := new(SomeStruct) // 也相同
公平的是,评论者指出了这种语言上的冗余,有时这会导致他们搜索其他不一致之处,最显着的是 make
和 new
之间的冗余。
从表面上看,make
和 new
做的事情非常相似,那么两者并存的理由是什么?
为什么我们不能对任何事物都使用 make ?
Go 没有用户定义的泛型类型,但是确实有几种内置类型可以用作泛型,lists(列表),maps(映射),sets(集合) 和 queues(队列);(slices)切片,maps(映射)和 channels(通道)。
由于 make
旨在创建这三种内置泛型类型,因此必须由运行时提供,因为无法在 Go 中直接表达make
的功能签名。
尽管 make
创建了泛型切片,映射和通道值,但它们仍然只是常规值; make
不返回指针值。
如果为有利于 make
而删除 new
,您将如何构造指向初始值的指针?
var x1 *int
var x2 = new(int)
x1
和 x2
具有相同的类型,*int
,x2
指向初始化的内存,并且可以安全地取消引用,相同的是对于 x1
都不为 true。
为什么我们不能对任何事物都使用 new ?
尽管 new
很少使用,但其行为已经被很好地指定了。
new(T)
始终返回一个 * T
指向已初始化的T
。由于 Go 没有构造函数,因此该值将被初始化为T
的 零值。
使用 new
构造指向切片,映射或通道零值的指针,目前可以使用并且与 new
的行为一致。
s := new([]string)
fmt.Println(len(*s)) // 0
fmt.Println(*s == nil) // true
m := new(map[string]int)
fmt.Println(m == nil) // false
fmt.Println(*m == nil) // true
c := new(chan int)
fmt.Println(c == nil) // false
fmt.Println(*c == nil) // true
当然可以,这些只是规则,我们可以更改它们,对吧?
对于它们可能造成的混乱,make
和new
是一致的;make
仅创建切片,映射和通道,new
仅返回指向初始化内存的指针。
是的,对于切片,映射和通道,new
可以扩展为像 make
一样运行,但这会引入自身的不一致之处。
- 如果传递给
new
的类型是一个切片,映射或 通道,则new
将具有特殊的行为。这是每个 Go 程序员都必须记住的规则。 - 对于切片和映射,
new
必须变得可变,并根据需要采用可能的长度,缓冲区大小或容量。再次需要记住更多的特殊情况,而在new
之前,只有一个参数,即类型。 new
始终为传递给它的T
返回一个*T
。那意味着代码像
func Read(buf []byte) []byte
// 假设 new 需要一个可选的长度
buf := Read(new([]byte, 4096))
这样的代码将不再可能,需要在语法中使用更多特殊情况才能允许 *new([]byte,length)
。
综上所述
make
和 new
做不同的事情。
如果您是从另一种语言转过来的,尤其是使用构造函数的语言,那么看来 new
应该是您所需要的,但是 Go 并不是那些语言,也没有构造函数。
我的建议是尽量保守地使用 new
,因为不用它,几乎总是有更简单或更干净的方法来编写程序。
作为代码审查者,使用 new
就像使用命名返回参数一样,表明该代码正在尝试做一些聪明的事情,我需要特别注意。可能代码确实很聪明,但更可能的是,它可以重写得更清楚、更符合习惯。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: