Types 在Go中实例化类型的首选方法

Types 在Go中实例化类型的首选方法,types,go,declaration,instantiation,idioms,Types,Go,Declaration,Instantiation,Idioms,我喜欢这样一个事实,Go没有给我一百万种做简单事情的方法——借用Python的禅宗,“应该有一种——最好只有一种——显而易见的方法来做。” 但是,我不清楚实例化类型的首选/惯用方式。基本类型很简单: n := 0 t := 1.5 str := "Hello" 但是结构呢?以下是否等效,如果是,首选哪一种,为什么 var f Foo f := Foo{} 切片怎么样?我可以做var xs[]int,xs:=[]int{},或者xs:=make([]int),但是我认为第一个选项(与结

我喜欢这样一个事实,Go没有给我一百万种做简单事情的方法——借用Python的禅宗,“应该有一种——最好只有一种——显而易见的方法来做。”

但是,我不清楚实例化类型的首选/惯用方式。基本类型很简单:

n := 0
t := 1.5
str := "Hello"
但是结构呢?以下是否等效,如果是,首选哪一种,为什么

var f Foo    
f := Foo{}
切片怎么样?我可以做
var xs[]int
xs:=[]int{}
,或者
xs:=make([]int)
,但是我认为第一个选项(与结构相反)与其他选项不同?我想这也适用于地图

对于指针,我听说应该避免使用
new
。这是一个好建议吗?如果是的话,什么才算是
new
的有效用法


我意识到这在一定程度上可能是一个风格的问题,但在任何情况下,选择特定风格的理由都是有帮助的。

您可以查看Go标准库的源代码,在那里可以找到许多惯用的Go代码

您是对的:
var xs[]int
不同于其他两个变量,因为它不“初始化”xs,xs为nil。而另外两个则真正构成了一个切片
xs:=[]int{}
如果您需要一个具有零上限的空片,那么它很常见,而
make
提供了更多选项:长度和容量。另一方面,通常以nil片开始,然后通过追加
var s[]int来填充;对于{s=append(s,num)}


new
无法避免,因为这是创建指针(例如指向uint32或其他内置类型)的唯一方法。但是你是对的,写
a:=new(a)
是非常不常见的,而且大部分写为
a:=&a{}
,因为这可以变成
a:=&a{n:17,不管什么:“foo”}
。使用
new
并不是真的不受欢迎,但考虑到struct文本的能力,我觉得它只是Java的一个遗留问题

在Google IO上与围棋小组在炉边聊天时,观众中有人问围棋小组他们想从语言中获得什么

Rob说他希望有更少的方法来声明变量,并提到:


冒号等于overwrite,命名为result parameters(),在for循环中重用的变量容易混淆,尤其是对于闭包。但是,语言可能不会有太大变化。

当您声明变量时,其中
T
是某种类型:

var name T
Go为您提供一段未初始化的“零”内存

对于原语,这意味着
var name int
将为0,
var name string
将为“”。在里面Go保证未初始化的变量是该类型的零等效变量

内部切片、贴图和通道被视为指针。指针的零值是nil,这意味着它指向nil内存。如果不初始化它,如果您尝试对其进行操作,可能会遇到恐慌

make
功能是专门为切片、贴图或频道设计的。make函数的参数为:

make(T type, length int[, capacity int]) // For slices.
make(T[, capacity int]) // For a map.
make(T[, bufferSize int]) // For a channel. How many items can you take without blocking?
A slices
length
是它以多少项开始。容量是在需要调整大小之前分配的内存(内部,新大小*2,然后复制)。有关更多信息,请参阅

结构:
new(T)
相当于
&T{}
,而不是
T{}
*new(T)
相当于
*&T{}

切片:
make([]T,0)
相当于
[]T{}

映射:
make(map[T]T)
相当于
map[T]T{}

至于首选哪种方法,我问自己以下问题:

我现在知道函数中的值吗

如果答案是“是”,那么我选择上面的一个
T{…}
。如果答案是“否”,那么我使用make或new

例如,我会避免这样的事情:

type Name struct {
    FirstName string
    LastName string
}

func main() {
    name := &Name{FirstName:"John"}
    // other code...
    name.LastName = "Doe"
}
func main() {
    name := new(Name)
    name.FirstName = "John"
    // other code...
    name.LastName = "Doe"
}
相反,我会这样做:

type Name struct {
    FirstName string
    LastName string
}

func main() {
    name := &Name{FirstName:"John"}
    // other code...
    name.LastName = "Doe"
}
func main() {
    name := new(Name)
    name.FirstName = "John"
    // other code...
    name.LastName = "Doe"
}
为什么??因为通过使用
new(Name)
我清楚地表明我打算稍后填充这些值。如果我使用了
&Name{…}
,就不清楚我是否打算稍后在同一个函数中添加/更改一个值,而不读取代码的其余部分

当您不需要指针时,结构例外。我将使用
T{}
,但如果我计划添加/更改值,我不会在其中添加任何内容。当然,
*new(T)
也可以,但这就像使用
*&T{}
一样<在这种情况下,code>T{}更干净,尽管我倾向于使用带结构的指针,以避免在传递它时生成副本

另一件需要记住的事情是,
[]*struct
[]struct
更小,调整大小也更便宜,假设struct比指针大得多,指针通常是4-8字节(64位8字节?)
  • var xs[]int
  • xs:=[]int{}
  • xs:=make([]int,2)
  • 我避免使用第三项,除非我需要声明尺寸:

    xs := make([]int, 2)
    xs[1] = 100
    
    我避免使用第二项,除非我的值包括:

    xs := []int{9, 8}
    
    xs := map[string]int{"month": 12, "day": 31}
    
    f := Foo{31}
    f := Foo{Day: 31}
    
    f := &Foo{31}
    f := &Foo{Day: 31}
    
    地图
  • xs:=make(映射[string]int)
  • xs:=map[string]int{}
  • 我避免使用第二项,除非我的值包括:

    xs := []int{9, 8}
    
    xs := map[string]int{"month": 12, "day": 31}
    
    f := Foo{31}
    f := Foo{Day: 31}
    
    f := &Foo{31}
    f := &Foo{Day: 31}
    
    结构
  • var f Foo
  • f:=Foo{}
  • 我避免使用第二项,除非我的值包括:

    xs := []int{9, 8}
    
    xs := map[string]int{"month": 12, "day": 31}
    
    f := Foo{31}
    f := Foo{Day: 31}
    
    f := &Foo{31}
    f := &Foo{Day: 31}
    
    指针
  • var f Foo&f
  • f:=new(Foo)
  • f:=&Foo{}
  • 我避免使用第三项,除非我有以下值:

    xs := []int{9, 8}
    
    xs := map[string]int{"month": 12, "day": 31}
    
    f := Foo{31}
    f := Foo{Day: 31}
    
    f := &Foo{31}
    f := &Foo{Day: 31}
    
    我避免使用第二项,除非变量的每次使用都处于“指针模式”:


    +我并没有真正回答这个问题,但我确实同意——很高兴知道派克自己这么说。这是我在GoLang身上发现的一个弱点:有太多的方法来声明,却不清楚它们各自的优缺点和适用性——有时给我一种“还没完成”的感觉。