t=&T{} 和 t=new(T) 之间的核心区别是什么
What is the core difference between t=&T{} and t=new(T)
似乎这两种方法都可以创建一个成员值为“0”的新对象指针,都是returns一个指针:
type T struct{}
...
t1:=&T{}
t2:=new(T)
那么 t1 和 t2 之间的核心区别是什么,或者有什么是 "new" 可以做而 &T{} 不能做的,反之亦然?
对于结构体和其他复合体,两者是相同的。
t1:=&T{}
t2:=new(T)
//Both are same
您不能 return 未命名变量的地址在不使用 new
的情况下初始化为其他基本类型(如 int)的零值。您需要创建一个命名变量,然后获取其地址。
func newInt() *int {
return new(int)
}
func newInt() *int {
// return &int{} --> invalid
var dummy int
return &dummy
}
[…] is there anything that "new" can do while &T{} cannot, or vice versa?
我能想到三个区别:
- "composite literal" 语法(
&T{}
的 T{}
部分)仅适用于 "structs, arrays, slices, and maps" [link], whereas the new
function works for any type [link].
- 对于结构或数组类型,
new
函数始终为其元素生成零值,而复合文字语法允许您根据需要将某些元素初始化为非零值。
- 对于切片或映射类型,
new
函数始终 returns 指向 nil
的指针,而复合文字语法始终 returns 初始化的切片或映射. (对于映射,这是非常重要的,因为您不能向 nil
添加元素。)此外,复合文字语法甚至可以创建一个 非 空切片或映射。
(第二个和第三个要点实际上是同一件事的两个方面——new
函数总是创建零值——但我将它们分开列出,因为不同的含义有点不同类型。)
参见 ruakh's answer。不过,我想指出一些内部实现细节。您不应该在生产代码中使用它们,但它们有助于阐明幕后真正发生的事情,在 Go 运行 时间。
本质上,一个切片由三个值表示。 reflect
包导出一个类型,SliceHeader
:
SliceHeader is the runtime representation of a slice. It cannot be used safely or portably and its representation may change in a later release. Moreover, the Data field is not sufficient to guarantee the data it references will not be garbage collected, so programs must keep a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
如果我们用它来检查[]T
类型的变量(对于任何类型T
),我们可以看到三个部分:指向底层数组的指针、长度和容量。在内部,切片值 v
总是包含所有这三个部分。有一个一般条件我认为应该成立,如果不使用unsafe
打破它,看来会 保持(无论如何基于有限的测试):
- 要么
Data
字段不为零(在这种情况下 Len
和 Cap
可以但不必非零),或者
Data
字段为零(在这种情况下,Len
和 Cap
都应为零)。
如果 Data
字段为零,则切片值 v
为 nil
。
通过使用 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).
代码本身有点长所以我把它留到最后(或者使用上面的 link 到 Playground)。但它表明源代码中的实际 p == nil
测试编译为仅检查 Data
字段。
当你这样做时:
p2 := new([]int)
new
函数实际上只分配切片 header。它将所有三个部分设置为零,并将 returns 指向结果 header 的指针。所以 *p2
中有三个零字段,这使它成为正确的 nil
值。
另一方面,当您这样做时:
p1 := &[]int{}
Go 编译器构建一个空数组(大小为零,包含零个整数),然后构建一个切片 header:指针部分指向空数组,长度和容量设置为零.然后 p1
指向这个 header,带有 non-nil Data
字段。稍后的赋值 *p1 = nil
将零写入所有三个字段。
让我用粗体重复一遍:语言规范并未承诺这些,它们只是实际的实施。
地图的工作原理非常相似。映射变量实际上是指向映射header的指针。 map headers 的详细信息甚至比 slice headers 的详细信息更难访问:它们没有 reflect
类型。实际实现可以在type hmap
下查看here(注意不是导出)。
这意味着 m2 := new(map[T1]T2)
实际上只分配了一个 指针 ,并将该指针本身设置为 nil。没有实际地图! new
函数returns nil指针,然后m2
就是nil
。同样,var m1 map[T1]T2
只是将 m1
中的一个简单指针值设置为 nil
。但是 var m3 map[T1]T2{}
分配了一个实际的 hmap
结构,填充它,并使 m3
指向它。我们可以再次 peek behind the curtain on the Go Playground,使用不能保证明天工作的代码,看看它的效果。
作为编写 Go 程序的人,您不需要知道这些。但是,如果您使用过 lower-level 语言(例如汇编语言和 C),那么这些解释很多。特别是,这些解释了 为什么 你不能插入到 nil 映射中:map 变量本身 持有一个指针值,直到 map 变量本身 有一个non-nil 指针指向一个(可能为空)map-header,没有办法进行插入。插入可以分配一个新映射并插入数据,但是 map 变量 不会指向正确的 hmap
header object.
(语言作者本可以通过使用二级间接来完成这项工作:地图变量可以是指向指向地图 header 的变量的指针。或者他们可以制作地图变量总是指向一个 header,并使 new
实际上分配了一个 header,就像 make
那样;那么永远不会有 nil 映射。但他们没有做其中任何一个,我们得到我们得到的,这很好:你只需要知道初始化地图。)
这是切片检查器。 (使用 playground link 查看地图检查器:考虑到我不得不从 运行 时间复制 hmap
的定义,我预计它会特别脆弱,不值得展示. 切片 header 的结构似乎不太可能随时间改变。)
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
}
似乎这两种方法都可以创建一个成员值为“0”的新对象指针,都是returns一个指针:
type T struct{}
...
t1:=&T{}
t2:=new(T)
那么 t1 和 t2 之间的核心区别是什么,或者有什么是 "new" 可以做而 &T{} 不能做的,反之亦然?
对于结构体和其他复合体,两者是相同的。
t1:=&T{}
t2:=new(T)
//Both are same
您不能 return 未命名变量的地址在不使用 new
的情况下初始化为其他基本类型(如 int)的零值。您需要创建一个命名变量,然后获取其地址。
func newInt() *int {
return new(int)
}
func newInt() *int {
// return &int{} --> invalid
var dummy int
return &dummy
}
[…] is there anything that "new" can do while &T{} cannot, or vice versa?
我能想到三个区别:
- "composite literal" 语法(
&T{}
的T{}
部分)仅适用于 "structs, arrays, slices, and maps" [link], whereas thenew
function works for any type [link]. - 对于结构或数组类型,
new
函数始终为其元素生成零值,而复合文字语法允许您根据需要将某些元素初始化为非零值。 - 对于切片或映射类型,
new
函数始终 returns 指向nil
的指针,而复合文字语法始终 returns 初始化的切片或映射. (对于映射,这是非常重要的,因为您不能向nil
添加元素。)此外,复合文字语法甚至可以创建一个 非 空切片或映射。
(第二个和第三个要点实际上是同一件事的两个方面——new
函数总是创建零值——但我将它们分开列出,因为不同的含义有点不同类型。)
参见 ruakh's answer。不过,我想指出一些内部实现细节。您不应该在生产代码中使用它们,但它们有助于阐明幕后真正发生的事情,在 Go 运行 时间。
本质上,一个切片由三个值表示。 reflect
包导出一个类型,SliceHeader
:
SliceHeader is the runtime representation of a slice. It cannot be used safely or portably and its representation may change in a later release. Moreover, the Data field is not sufficient to guarantee the data it references will not be garbage collected, so programs must keep a separate, correctly typed pointer to the underlying data.
type SliceHeader struct { Data uintptr Len int Cap int }
如果我们用它来检查[]T
类型的变量(对于任何类型T
),我们可以看到三个部分:指向底层数组的指针、长度和容量。在内部,切片值 v
总是包含所有这三个部分。有一个一般条件我认为应该成立,如果不使用unsafe
打破它,看来会 保持(无论如何基于有限的测试):
- 要么
Data
字段不为零(在这种情况下Len
和Cap
可以但不必非零),或者 Data
字段为零(在这种情况下,Len
和Cap
都应为零)。
如果 Data
字段为零,则切片值 v
为 nil
。
通过使用 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).
代码本身有点长所以我把它留到最后(或者使用上面的 link 到 Playground)。但它表明源代码中的实际 p == nil
测试编译为仅检查 Data
字段。
当你这样做时:
p2 := new([]int)
new
函数实际上只分配切片 header。它将所有三个部分设置为零,并将 returns 指向结果 header 的指针。所以 *p2
中有三个零字段,这使它成为正确的 nil
值。
另一方面,当您这样做时:
p1 := &[]int{}
Go 编译器构建一个空数组(大小为零,包含零个整数),然后构建一个切片 header:指针部分指向空数组,长度和容量设置为零.然后 p1
指向这个 header,带有 non-nil Data
字段。稍后的赋值 *p1 = nil
将零写入所有三个字段。
让我用粗体重复一遍:语言规范并未承诺这些,它们只是实际的实施。
地图的工作原理非常相似。映射变量实际上是指向映射header的指针。 map headers 的详细信息甚至比 slice headers 的详细信息更难访问:它们没有 reflect
类型。实际实现可以在type hmap
下查看here(注意不是导出)。
这意味着 m2 := new(map[T1]T2)
实际上只分配了一个 指针 ,并将该指针本身设置为 nil。没有实际地图! new
函数returns nil指针,然后m2
就是nil
。同样,var m1 map[T1]T2
只是将 m1
中的一个简单指针值设置为 nil
。但是 var m3 map[T1]T2{}
分配了一个实际的 hmap
结构,填充它,并使 m3
指向它。我们可以再次 peek behind the curtain on the Go Playground,使用不能保证明天工作的代码,看看它的效果。
作为编写 Go 程序的人,您不需要知道这些。但是,如果您使用过 lower-level 语言(例如汇编语言和 C),那么这些解释很多。特别是,这些解释了 为什么 你不能插入到 nil 映射中:map 变量本身 持有一个指针值,直到 map 变量本身 有一个non-nil 指针指向一个(可能为空)map-header,没有办法进行插入。插入可以分配一个新映射并插入数据,但是 map 变量 不会指向正确的 hmap
header object.
(语言作者本可以通过使用二级间接来完成这项工作:地图变量可以是指向指向地图 header 的变量的指针。或者他们可以制作地图变量总是指向一个 header,并使 new
实际上分配了一个 header,就像 make
那样;那么永远不会有 nil 映射。但他们没有做其中任何一个,我们得到我们得到的,这很好:你只需要知道初始化地图。)
这是切片检查器。 (使用 playground link 查看地图检查器:考虑到我不得不从 运行 时间复制 hmap
的定义,我预计它会特别脆弱,不值得展示. 切片 header 的结构似乎不太可能随时间改变。)
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
}