似乎两种创建具有所有“ 0”成员值的新对象指针的方法都返回了一个指针:
type T struct{}
...
t1:=&T{}
t2:=new(T)
那么t1和t2之间的核心区别是什么?或者&T {}不能做到,“ new”可以做什么,反之亦然?
答案 0 :(得分:4)
[…]“&new”有什么能做的,而&T {}却不能做,反之亦然?
我可以想到三个区别:
T{}
的{{1}}部分)仅适用于“结构,数组,切片和映射” [link],而{{1 }}函数适用于任何类型的[link]。&T{}
函数始终为其元素生成零值,而复合文字语法使您可以根据需要将某些元素初始化为非零值。new
函数始终返回指向new
的指针,而复合文字语法始终返回初始化的切片或地图。 (对于地图而言,这非常重要,因为您无法将元素添加到new
中。)此外,复合文字语法甚至可以创建 non -empty slice或map。(第二点和第三点实际上是同一件事的两个方面-nil
函数始终创建零值-但我将它们分开列出是因为不同类型的含义有所不同。 )
答案 1 :(得分:0)
对于结构和其他复合材料,两者相同。
t1:=&T{}
t2:=new(T)
//Both are same
如果不使用new
,则不能返回初始化为其他基本类型(如int)的零值的未命名变量的地址。您将需要创建一个命名变量,然后获取其地址。
func newInt() *int {
return new(int)
}
func newInt() *int {
// return &int{} --> invalid
var dummy int
return &dummy
}
答案 2 :(得分:0)
请参见ruakh's answer。我想指出一些内部实现细节。您不应该在生产代码中使用它们,但是它们有助于阐明Go运行时在幕后发生的真正情况。
基本上,一个切片由三个值表示。 reflect
软件包导出类型SliceHeader
:
SliceHeader是切片的运行时表示形式。它不能安全或便携地使用,其表示形式可能会在以后的版本中更改。而且,“数据”字段不足以保证不会对其进行垃圾回收,因此程序必须保留一个单独的,正确键入的指向基础数据的指针。
type SliceHeader struct { Data uintptr Len int Cap int }
如果使用此方法检查类型为[]T
的变量(对于任何类型为T
的变量),则可以看到三个部分:指向基础数组的指针,长度和容量。在内部,切片值v
始终具有所有这三个部分。我认为一般情况下应该成立,如果您不使用unsafe
来打破它,通过检查看来,它将会成立(基于仍在有限的测试中):
Data
字段不为零(在这种情况下,Len
和Cap
可以但不一定非零),或者Data
字段为零(在这种情况下,Len
和Cap
都应为零)。如果v
字段为零,则切片值nil
为Data
。
通过使用unsafe
程序包,我们可以有意地将其破坏(然后将其放回原位,并希望在破坏它的过程中不会出错),然后检查这些碎片。运行this code on the Go Playground(下面还有一个副本)时,它会打印:
via &literal: base of array is 0x1e52bc; len is 0; cap is 0.
Go calls this non-nil.
via new: base of array is 0x0; len is 0; cap is 0.
Go calls this nil even though we clobbered len() and cap()
Making it non-nil by unsafe hackery, we get [42] (with cap=1).
after setting *p1=nil: base of array is 0x0; len is 0; cap is 0.
Go calls this nil even though we clobbered len() and cap()
Making it non-nil by unsafe hackery, we get [42] (with cap=1).
代码本身有点长,所以我将其留在了结尾(或使用上面的Playground链接)。但是它表明源中的实际p == nil
测试只是对Data
字段的检查。
操作时:
p2 := new([]int)
new
函数实际上仅分配切片 header 。它将所有三个部分设置为零,并将指针返回到结果标头。因此*p2
中有三个零字段,这使其成为正确的nil
值。
另一方面,当您这样做时:
p1 := &[]int{}
Go编译器将构建一个空数组(大小为零,包含零个整数),然后构建一个slice头:指针部分指向该空数组,并且长度和容量被设置为零。然后p1
指向该标头,其中字段{nil}为非零。稍后的分配Data
将零写入所有三个字段。
让我用黑体字重复一下:语言规范没有承诺这些,它们只是实际的实现方式。
地图的工作方式非常相似。 map变量实际上是指向 map标头的指针。映射标头的详细信息甚至比切片标头的详细信息更难访问:它们没有*p1 = nil
类型。实际的实现在reflect
下可见here(请注意,它不会导出)。
这意味着type hmap
实际上仅分配了一个指针,并将该指针本身设置为nil。没有实际的地图! m2 := new(map[T1]T2)
函数返回nil指针,然后new
为m2
。同样,nil
只是将var m1 map[T1]T2
中的简单指针值设置为m1
。但是nil
分配了一个实际的var m3 map[T1]T2{}
结构,将其填充,并使hmap
指向它。我们可以再次peek behind the curtain on the Go Playground(使用不能保证明天能正常工作的代码)来了解这一点。
作为编写Go程序的人,您不需要知道任何这些。但是,如果您使用的是较低级的语言(例如汇编语言和C语言),则这些内容将说明很多问题。特别是,这些解释了为什么您不能插入到nil映射中的原因: map变量本身持有一个指针值,直到 map变量本身有一个非nil指向(可能为空)映射头的指针,无法进行插入。插入可以分配新的地图并插入数据,但是 map变量不会指向正确的m3
标头对象。
(语言作者可以通过使用第二级间接性来完成这项工作:映射变量可以是指向指向映射标头的变量的指针。或者,他们可以使映射变量始终指向标头。 ,并使hmap
实际上像new
那样分配标题;这样就永远不会有nil映射,但是他们都不做任何一个,我们得到的就是,很好:您只需要知道初始化地图即可。)
这是切片检查器。 (使用游乐场链接查看地图检查器:由于我不得不从运行时中复制make
的定义,因此我希望它特别脆弱,不值得显示。slice标题的结构似乎不太可能随时间变化。)
hmap