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

Pointers t=&t{}和t=new(t)之间的核心区别是什么,pointers,go,struct,new-operator,Pointers,Go,Struct,New Operator,似乎创建包含所有0成员值的新对象指针的两种方法都会返回指针: type T struct{} ... t1:=&T{} t2:=new(T) 那么t1和t2之间的核心区别是什么,或者new可以做什么而&T{}不能做什么,或者反之亦然?对于结构和其他复合结构,两者都是相同的 t1:=&T{} t2:=new(T) //Both are same 如果不使用new,则无法将初始化为其他基本类型(如int)的零值的未命名变量的地址返回。您需要创建一个命名变量,然后获取其地址 fun

似乎创建包含所有0成员值的新对象指针的两种方法都会返回指针:

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

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

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

t1:=&T{}
t2:=new(T)
//Both are same
如果不使用new,则无法将初始化为其他基本类型(如int)的零值的未命名变量的地址返回。您需要创建一个命名变量,然后获取其地址

func newInt() *int {            
    return new(int)                 
}               

[…]当&T{}做不到的时候,new可以做什么,或者反之亦然

我可以想到三个不同点:

&T{}的T{}部分的复合文字语法仅适用于结构、数组、切片和映射[],而新函数适用于任何类型[]。 对于结构或数组类型,新函数总是为其元素生成零值,而复合文字语法允许您根据需要将某些元素初始化为非零值。 对于切片或映射类型,新函数始终返回指向nil的指针,而复合文字语法始终返回初始化的切片或映射。对于贴图,这非常重要,因为不能将元素添加到nil。此外,复合文字语法甚至可以创建非空切片或映射。 第二个和第三个要点实际上是同一事物的两个方面——新函数总是创建零值——但我单独列出它们,因为不同类型的含义有点不同。

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

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

SliceHeader是切片的运行时表示形式。它不能安全或可移植地使用,其表示形式可能会在以后的版本中更改。此外,数据字段不足以保证它引用的数据不会被垃圾收集,因此程序必须保留一个单独的、正确键入的指向底层数据的指针

type SliceHeader struct {
        Data uintptr
        Len  int
        Cap  int
}
如果我们使用它来检查任何类型T的[]T类型的变量,我们可以看到三个部分:指向底层数组的指针、长度和容量。在内部,切片值v始终包含这三个部分。我认为有一个一般情况应该保持,如果你不使用“不安全”来破坏它,那么通过检查,它似乎无论如何都会保持在有限测试的基础上:

数据字段不为零,在这种情况下,Len和Cap可以但不必为非零,或者 数据字段为零,在这种情况下,Len和Cap都应为零。 如果数据字段为零,则切片值v为零

通过使用不安全的包装,我们可以故意将其破坏,然后将其全部放回原处,希望在我们将其破坏时不会出现任何问题,从而检查零件。运行时,下面也有一个副本,它会打印:

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).
代码本身有点长,所以我把它留到最后,或者使用上面的链接到游乐场。但它表明,源代码中的实际p==nil测试编译为对数据字段的检查

当您这样做时:

p2 := new([]int)
p1 := &[]int{}
新函数实际上只分配片头。它将所有三个部分设置为零,并返回指向结果头的指针。所以*p2中有三个零字段,这使得它是一个正确的nil值

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

p2 := new([]int)
p1 := &[]int{}
Go编译器构建一个大小为零的空数组,保存零整数,然后构建一个切片头:指针部分指向空数组,长度和容量设置为零。然后p1指向这个标题,带有非nil数据字段。后面的赋值*p1=nil将零写入所有三个字段

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

地图的工作原理非常相似。映射变量实际上是指向映射头的指针。映射头的细节甚至比切片头的细节更难访问:它们没有反射类型。实际实现可以在hmap类型下查看。请注意,它没有导出

这意味着m2:=newmap[T1]T2实际上只分配一个指针,并将该指针本身设置为nil。没有真实的地图!新函数返回nil指针,然后m2为nil。同样,var m1 map[T1]T2只是将m1中的一个简单指针值设置为nil。但是VARM3Map[T1]T2{}分配了一个实际的hmap结构,填充它,并使m3指向它。我们可以再次看到,对于明天不能保证工作的代码,这一点是有效的

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

语言作者可以通过使用第二级间接寻址来完成这项工作:映射变量可以是指向指向映射头的变量的指针。或者他们可以让map变量总是指向一个头,让new实际分配一个头,就像make那样;那就永远不会有零地图。但是他们没有做这两件事,我们得到了我们得到的,这很好:你只需要知道初始化地图

这是切片检查员。使用游乐场链接查看地图检查器:鉴于我必须在运行时外复制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
}

对于struct类型,情况并非如此:func newT1*T1{return&T1{}编译并运行良好,这就是我的答案。它适用于结构类型,但不适用于其他基本类型,如int。从技术上讲,新切片或映射初始化为所有零值。当map或slice的元素本身是指针,但通常是有保证的时,这个技术点尤其重要。@torek:语言规范与您不一致。new将值初始化为零这一事实与复合文本有什么关系?也许我不清楚我说的新切片或地图是指由new创建的新切片或地图。。。啊,也许你说的是new创建的切片是未初始化的。新切片的长度为零,容量为零。我认为,如果长度和容量被初始化,这意味着切片被初始化。在内部,它里面的不安全指针是nil,这也是在一个重要的内部意义上初始化的。但另一方面,new与make非常不同,make将所有可访问的值都设置为零。基本上,所有这些都是你可以通过拨动不安全的包来发现的,所以这并不是一个明显的区别:语言并没有以这样或那样的方式承诺事情。但对于内部gc代码来说,这关系到效率。