为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
慕田峪4524236 2022-05-05 18:02:33
似乎使用所有“0”成员值创建新对象指针的两种方法都返回一个指针:type T struct{}...t1:=&T{}t2:=new(T)那么 t1 和 t2 之间的核心区别是什么,或者有什么“新”可以做而 &T{} 不能做的事情,反之亦然?
查看完整描述

3 回答

?
海绵宝宝撒

TA贡献1809条经验 获得超8个赞

[...] 有什么“新”可以做而 &T{} 不能做的事情,反之亦然?

我能想到三个不同点:

  • “复合文字”语法(的T{}一部分&T{})仅适用于“结构、数组、切片和映射”[链接],而该new函数适用于任何类型[链接]。

  • 对于结构或数组类型,new函数始终为其元素生成零值,而复合文字语法允许您根据需要将某些元素初始化为非零值。

  • 对于切片或映射类型,new函数始终返回指向 的指针nil,而复合文字语法始终返回已初始化的切片或映射。(对于地图来说,这非常重要,因为您不能向 . 中添加元素nil。)此外,复合字面量语法甚至可以创建空切片或地图。

(第二个和第三个要点实际上是同一件事的两个方面——new函数总是创建零值——但我将它们分开列出,因为不同类型的含义有点不同。)


查看完整回答
反对 回复 2022-05-05
?
肥皂起泡泡

TA贡献1829条经验 获得超6个赞

见ruak 的回答。不过,我想指出一些内部实现细节。你不应该在生产代码中使用它们,但它们有助于阐明幕后真正发生的事情,在 Go 运行时。


本质上,切片由三个值表示。包reflect导出一个类型,SliceHeader:


SliceHeader 是切片的运行时表示。它不能安全或便携地使用,它的表示可能会在以后的版本中发生变化。此外,Data 字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保留一个单独的、正确类型的指向底层数据的指针。


type SliceHeader struct {

        Data uintptr

        Len  int

        Cap  int

}

如果我们使用它来检查类型变量[]T(对于任何类型T),我们可以看到三个部分:指向底层数组的指针、长度和容量。在内部,切片值v始终具有所有这三个部分。我认为应该保持一个一般条件,如果你不使用unsafe它来打破它,通过检查它似乎会保持(无论如何基于有限的测试):


该Data字段不为零(在这种情况下Len,并且Cap可以但不必非零),或者

该Data字段为零(在这种情况下Len和Cap都应该为零)。

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


通过使用unsafe包装,我们可以故意破坏它(然后将其全部放回原处——希望在我们破坏它的同时不会出现任何问题),从而检查碎片。当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函数实际上只分配切片头。它将所有三个部分设置为零并返回指向结果标头的指针。因此*p2,其中包含三个零字段,这使其成为正确的nil值。


另一方面,当你这样做时:


p1 := &[]int{}

Go 编译器构建一个空数组(大小为零,保持零整数),然后构建一个切片头:指针部分指向空数组,长度和容量设置为零。然后p1使用非零Data字段指向此标头。稍后的赋值*p1 = nil将零写入所有三个字段。


让我用粗体字重复一遍:这些不是语言规范所承诺的,它们只是实际的实现。


地图的工作方式非常相似。map 变量实际上是一个指向map header的指针。map headers 的细节比 slice headers 更难访问:它们没有reflect类型。实际实现可在此处type hmap查看(请注意,它未导出)。


这意味着m2 := new(map[T1]T2)实际上只分配一个指针,并将该指针本身设置为 nil。没有实际的地图!该new函数返回 nil 指针,然后m2是nil. 同样,只需在tovar m1 map[T1]T2中设置一个简单的指针值。但是分配一个实际的结构,填充它,并指出它。我们可以再次窥视 Go Playground 的幕后,使用不保证明天可以工作的代码,看看它的效果。m1nilvar m3 map[T1]T2{}hmapm3


作为编写 Go 程序的人,你不需要知道这些。但是,如果您使用过低级语言(例如汇编和 C),这些可以解释很多。特别是,这些解释了为什么不能插入到 nil 映射中:映射变量本身保存一个指针值,并且在映射变量本身有一个指向(可能为空的)映射头的非零指针之前,没有办法做插入。插入可以分配一个新映射并插入数据,但映射变量不会指向正确的hmap标头对象。


(语言作者可以通过使用第二级间接来完成这项工作:映射变量可以是指向指向映射头的变量的指针。或者他们可以使映射变量始终指向头,并new实际上分配了一个标头,方式make是这样;那么永远不会有一个 nil 映射。但是他们没有做这些,我们得到了我们得到的,这很好:你只需要知道初始化映射。)


这是切片检查器。(使用操场链接查看地图检查器:鉴于我必须将hmap' 的定义从运行时复制出来,我希望它特别脆弱且不值得展示。切片头的结构似乎不太可能随着时间而改变。 )


package main


import (

    "fmt"

    "reflect"

    "unsafe"

)


func main() {

    p1 := &[]int{}

    p2 := new([]int)

    show("via &literal", *p1)

    show("\nvia new", *p2)

    *p1 = nil

    show("\nafter setting *p1=nil", *p1)

}


// This demonstrates that given a slice (p), the test

//    if p == nil

// is really a test on p.Data.  If it's zero (nil),

// the slice as a whole is nil.  If it's nonzero, the

// slice as a whole is non-nil.

func show(what string, p []int) {

    pp := unsafe.Pointer(&p)

    sh := (*reflect.SliceHeader)(pp)

    fmt.Printf("%s: base of array is %#x; len is %d; cap is %d.\n",

        what, sh.Data, sh.Len, sh.Cap)

    olen, ocap := len(p), cap(p)

    sh.Len, sh.Cap = 1, 1 // evil

    if p == nil {

        fmt.Println("  Go calls this nil even though we clobbered len() and cap()")

        answer := 42

        sh.Data = uintptr(unsafe.Pointer(&answer))

        fmt.Printf("  Making it non-nil by unsafe hackery, we get %v (with cap=%d).\n",

            p, cap(p))

        sh.Data = 0 // restore nil-ness

    } else {

        fmt.Println("Go calls this non-nil.")

    }

    sh.Len, sh.Cap = olen, ocap // undo evil

}


查看完整回答
反对 回复 2022-05-05
?
FFIVE

TA贡献1797条经验 获得超6个赞

对于结构和其他复合材料,两者都是相同的。


t1:=&T{}

t2:=new(T)

//Both are same

您不能在不使用new. 您需要创建一个命名变量,然后获取其地址。


func newInt() *int {            

    return new(int)                 

}               


func newInt() *int {

    // return &int{} --> invalid

    var dummy int

    return &dummy

}


查看完整回答
反对 回复 2022-05-05
  • 3 回答
  • 0 关注
  • 187 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号