简体   繁体   English

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

[英]Preferred way to instantiate types in Go

I like the fact that Go doesn't give me a million ways to do simple things – to borrow from The Zen of Python, “There should be one – and preferably only one – obvious way to do it.”我喜欢 Go 没有给我一百万种方法来做简单的事情这一事实——借用 Python 之禅,“应该有一种——最好只有一种——明显的方法来做这件事。”

However, I'm not clear on the preferred/idiomatic way of instantiating types.但是,我不清楚实例化类型的首选/惯用方式。 The basic types are easy:基本类型很简单:

n := 0
t := 1.5
str := "Hello"

But what about structs?但是结构呢? Are the following equivalent, and if so, which is preferred and why?以下是否等价,如果是,哪个更受欢迎,为什么?

var f Foo    
f := Foo{}

What about slices?切片呢? I can do var xs []int , xs := []int{} , or xs := make([]int) , but I think the first option (as opposed to with structs) is different from the others?我可以做var xs []intxs := []int{}xs := make([]int) ,但我认为第一个选项(与结构相反)与其他选项不同? I assume this will also apply to maps.我认为这也适用于地图。

With pointers, I hear that new should be avoided.通过指针,我听说应该避免使用new Is this good advice, and if so, what would count as a valid usage of new ?这是一个很好的建议吗?如果是这样,什么才算是new的有效用法?

I realize that this may partly be a question of style, but a rationale for preferring a particular style would be helpful in any case.我意识到这在一定程度上可能是风格问题,但在任何情况下,偏好特定风格的理由都会有所帮助。

When you declare a variable, where T is some type:当您声明一个变量时,其中T是某种类型:

var name T

Go gives you a piece of uninitialized "zeroed" memory. Go 给你一块未初始化的“归零”内存。

With primitives, this means that var name int would be 0, and var name string would be "".对于原语,这意味着var name int将为 0, var name string将为 ""。 In C it might be zeroed, or might be something unexpected .C 中,它可能被归零,或者可能是出乎意料的 Go guarantees an uninitialized variable is the type's zero equivalent. Go 保证未初始化的变量是该类型的零等价物。

Internally slices, maps, and channels are treated as pointers.在内部,切片、映射和通道被视为指针。 Pointers zero value is nil, meaning it points to nil memory.指针零值为零,意味着它指向零内存。 Without initializing it, you can encounter a panic if you try to operate on it.如果没有初始化它,如果您尝试对其进行操作,您可能会遇到恐慌。

The make function is specifically designed for a slice, map, or channel. make函数是专门为切片、贴图或通道设计的。 The make function's arguments are: 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 is how many items it starts with.切片length是它以多少个项目开始。 The capacity is the allocated memory before a resize is needed (internally, new size * 2, then copy).容量是在需要调整大小之前分配的内存(内部,新大小 * 2,然后复制)。 For more information see Effective Go: Allocation with make .有关更多信息,请参阅Effective Go:使用 make 分配

Structs: new(T) is equivalent to &T{} , not T{} .结构: new(T)等价于&T{} ,而不是T{} *new(T) is equivalent to *&T{} . *new(T)等价于*&T{}

Slices: make([]T,0) is equivalent to []T{} .切片: make([]T,0)等价于[]T{}

Maps: make(map[T]T) is equivalent to map[T]T{} .地图: make(map[T]T)相当于map[T]T{}

As far as which method is preferred, I ask myself the following question:至于首选哪种方法,我问自己以下问题:

Do I know the value(s) right now inside the function?我现在知道函数内的值吗?

If the answer is "yes", then I go with one of the above T{...} .如果答案是“是”,那么我会选择上述T{...} If the answer is "no", then I use make or new.如果答案是“否”,那么我使用 make 或 new。

For example, I would avoid something like this:例如,我会避免这样的事情:

type Name struct {
    FirstName string
    LastName string
}

func main() {
    name := &Name{FirstName:"John"}
    // other code...
    name.LastName = "Doe"
}

Instead I would do something like this:相反,我会做这样的事情:

func main() {
    name := new(Name)
    name.FirstName = "John"
    // other code...
    name.LastName = "Doe"
}

Why?为什么? Because by using new(Name) I make it clear that I intend to fill the values later.因为通过使用new(Name)我明确表示我打算稍后填充这些值。 If I used &Name{...} it wouldn't be clear that I intended to add/change a value later in the same function without reading the rest of the code.如果我使用&Name{...} ,我打算稍后在同一函数中添加/更改一个值而不阅读其余代码就不清楚了。

The exception is with structs when you don't want a pointer.当您不需要指针时,结构是例外。 I'll use T{} , but I won't put anything in it if I plan to add/change the values.我将使用T{} ,但如果我打算添加/更改值,我不会在其中放入任何内容。 Of course *new(T) also works, but that's like using *&T{} .当然*new(T)也有效,但这就像使用*&T{} T{} is cleaner in that case, although I tend to use pointers with structs to avoid making a copy when passing it around.在这种情况下T{}更干净,尽管我倾向于使用带有结构的指针来避免在传递时进行复制。

Another thing to keep in mind, a []*struct is smaller and cheaper to resize than []struct , assuming the struct is much larger than a pointer, which is typically 4 - 8 bytes (8 bytes on 64bit?).要记住的另一件事是,假设结构体比指针大得多,指针通常为 4 - 8 个字节(64 位上为 8 个字节?), []*struct[]struct更小且调整大小更便宜。

During the Fireside Chat with the Go Team at Google IO, someone in the audience asked the Go team what they would like to take out from the language.在 Google IO 与 Go 团队的炉边谈话中,观众中有人问 Go 团队他们想从语言中去掉什么。

Rob said he wished there was less way to declare variables and mentioned: Rob 说他希望有更少的方式来声明变量并提到:

Colon equals for overwrite, named result parameters ( https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU ), variable reused in a for loop being confusing, especially for closures.冒号等于覆盖,命名结果参数 ( https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU ),在 for 循环中重用的变量令人困惑,尤其是对于闭包。 However the language is probably not going to change much.然而,语言可能不会有太大变化。

You could have a look at the Go standard library sources where you can find lot of idiomatic Go code.您可以查看 Go 标准库源,在那里您可以找到许多惯用的 Go 代码。

You are right: var xs []int differs from the other two variants as it does not "initialize" xs, xs is nil.你是对的: var xs []int与其他两个变体不同,因为它不“初始化”xs,xs 为零。 While the other two really construct a slice.而另外两个确实构造了一个切片。 xs := []int{} is common if you need an empty slice with zero cap while make gives you more options: length and capacity. xs := []int{}如果您需要零上限的空切片,则很常见,而make为您提供更多选项:长度和容量。 On the other hand it is common to start with a nil slice and fill by appending as in var s []int; for ... { s = append(s, num) }另一方面,通常以 nil 切片开始并通过附加来填充,如var s []int; for ... { s = append(s, num) } var s []int; for ... { s = append(s, num) } . var s []int; for ... { s = append(s, num) }

new cannot be avoided total as it is the only way to create a pointer eg to uint32 or the other builtin types. new无法避免 total 因为它是创建指针的唯一方法,例如指向 uint32 或其他内置类型。 But you are right, writing a := new(A) is pretty uncommon and written mostly as a := &A{} as this can be turned into a := &A{n: 17, whatever: "foo"} .但是你是对的,写a := new(A)是非常不常见的,主要写成a := &A{}因为这可以变成a := &A{n: 17, whatever: "foo"} Usage of new is not really discouraged, but given the ability of struct literals it just looks like a leftover from Java to me.并没有真正阻止使用new ,但考虑到结构体字面量的能力,它对我来说只是 Java 的遗留物。

slice

  1. var xs []int
  2. xs := []int{}
  3. xs := make([]int, 2)

I avoid third item, unless I need to declare a size:我避免第三项,除非我需要声明尺寸:

xs := make([]int, 2)
xs[1] = 100

I avoid second item, unless I have values to include:我避免第二项,除非我有值要包括:

xs := []int{9, 8}

map地图

  1. xs := make(map[string]int)
  2. xs := map[string]int{}

I avoid second item, unless I have values to include:我避免第二项,除非我有值要包括:

xs := map[string]int{"month": 12, "day": 31}

struct结构

  1. var f Foo
  2. f := Foo{}

I avoid second item, unless I have values to include:我避免第二项,除非我有值要包括:

f := Foo{31}
f := Foo{Day: 31}

pointer指针

  1. var f Foo; &f
  2. f := new(Foo)
  3. f := &Foo{}

I avoid third item, unless I have values to include:我避免第三项,除非我有值要包括:

f := &Foo{31}
f := &Foo{Day: 31}

I avoid second item, unless every use of the variable will be in "pointer mode":我避免使用第二项,除非变量的每次使用都处于“指针模式”:

m, b := map[string]int{"month": 12, "day": 31}, new(bytes.Buffer)
json.NewEncoder(b).Encode(m)
http.Post("https://stackoverflow.com", "application/json", b)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM