t =&T {}和t = new(T)之间的核心区别是什么

时间:2020-01-19 06:48:40

标签: pointers go struct new-operator

似乎两种创建具有所有“ 0”成员值的新对象指针的方法都返回了一个指针:

type T struct{}
...
t1:=&T{}
t2:=new(T)

那么t1和t2之间的核心区别是什么?或者&T {}不能做到,“ new”可以做什么,反之亦然?

3 个答案:

答案 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字段不为零(在这种情况下,LenCap可以但不一定非零),或者
  • Data字段为零(在这种情况下,LenCap都应为零)。

如果v字段为零,则切片值nilData

通过使用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指针,然后newm2。同样,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
相关问题