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

huangapple go评论85阅读模式
英文:

Preferred way to instantiate types in Go

问题

我喜欢Go语言的一个特点,它不会给我提供一百种方法来做简单的事情 - 借用Python之禅的话来说,“应该有一种 - 最好只有一种 - 显而易见的方法来做它。”

然而,我对于实例化类型的首选/惯用方式不太清楚。基本类型很容易:

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

那么对于结构体呢?下面的两种方式等价吗?如果是的话,哪种方式更好,为什么?

var f Foo    
f := Foo{}

那么对于切片呢?我可以使用var xs []intxs := []int{},或者xs := make([]int),但我认为第一种选项(与结构体不同)与其他选项不同?我想这也适用于映射(maps)。

关于指针,我听说应该避免使用new。这是一个好建议吗?如果是的话,什么样的情况下可以使用new

我意识到这部分可能是关于风格的问题,但是对于偏好某种风格的理由在任何情况下都会很有帮助。

英文:

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.”

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? I assume this will also apply to maps.

With pointers, I hear that new should be avoided. Is this good advice, and if so, what would count as a valid usage of 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.

答案1

得分: 12

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

var name T

Go会给你一个未初始化的“零值”内存。

对于基本类型,这意味着var name int将为0,var name string将为""。在C中,它可能是零值,也可能是意外的值。Go保证未初始化的变量是该类型的零值等价物。

在内部,切片、映射和通道被视为指针。指针的零值是nil,表示它指向空内存。如果你尝试对未初始化的指针进行操作,可能会遇到panic。

make函数专门用于切片、映射或通道。make函数的参数是:

make(T类型, 长度int[, 容量int]) // 用于切片。
make(T[, 容量int]) // 用于映射。
make(T[, 缓冲区大小int]) // 用于通道。你可以在不阻塞的情况下获取多少个项目?

切片的长度是它开始时的项目数量。容量是在需要调整大小之前分配的内存(内部是新大小*2,然后复制)。更多信息请参见Effective Go: 使用make进行分配

结构体: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"}
    // 其他代码...
    name.LastName = "Doe"
}

而是这样做:

func main() {
    name := new(Name)
    name.FirstName = "John"
    // 其他代码...
    name.LastName = "Doe"
}

为什么?因为通过使用new(Name),我清楚地表明我打算稍后填充这些值。如果我使用&Name{...},则不清楚我打算在同一个函数中稍后添加/更改一个值,而不需要阅读其余的代码。

例外情况是在不想要指针的结构体中。我会使用T{},但如果我计划添加/更改值,我不会在其中放任何内容。当然,*new(T)也可以工作,但这就像使用*&T{}。在这种情况下,T{}更清晰,尽管我倾向于在结构体中使用指针,以避免在传递时进行复制。

还要记住,[]*struct[]struct更小且更便宜,假设结构体比指针大得多,而指针通常为4-8字节(64位上为8字节?)。

英文:

When you declare a variable, where T is some type:

var name T

Go gives you a piece of uninitialized "zeroed" memory.

With primitives, this means that var name int would be 0, and var name string would be "". In C it might be zeroed, or might be something unexpected. Go guarantees an uninitialized variable is the type's zero equivalent.

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. The make function's arguments are:

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. The capacity is the allocated memory before a resize is needed (internally, new size * 2, then copy). For more information see Effective Go: Allocation with make.

Structs: new(T) is equivalent to &T{}, not T{}. *new(T) is equivalent to *&T{}.

Slices: make([]T,0) is equivalent to []T{}.

Maps: make(map[T]T) is equivalent to 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{...}. If the answer is "no", then I use make or 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. 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.

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. Of course *new(T) also works, but that's like using *&T{}. T{} is cleaner in that case, although I tend to use pointers with structs to avoid making a copy when passing it around.

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?).

答案2

得分: 5

在Google IO的Go团队的炉边聊天中,观众中的某人问Go团队他们希望从语言中删除什么。

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

冒号等于用于覆盖,命名结果参数(https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU),在for循环中重复使用变量会令人困惑,尤其是对于闭包。然而,这门语言可能不会有太大的改变。

英文:

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.

Rob said he wished there was less way to declare variables and mentioned:

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. However the language is probably not going to change much.

答案3

得分: 4

你可以查看Go标准库的源代码,那里有很多典型的Go代码。

你是对的:var xs []int与其他两种变体不同,因为它不会“初始化”xs,xs是nil。而其他两种确实构造了一个切片。如果你需要一个容量为零的空切片,xs := []int{}是常见的用法,而make则提供了更多选项:长度和容量。另一方面,常见的做法是从一个nil切片开始,并通过追加来填充,例如var s []int; for ... { s = append(s, num) }

new是无法完全避免的,因为它是创建指向uint32或其他内置类型的指针的唯一方法。但你是对的,写成a := new(A)是相当不常见的,大多数情况下会写成a := &A{},因为这可以转换为a := &A{n: 17, whatever: "foo"}。并不是真的不鼓励使用new,但由于结构体字面量的能力,它看起来更像是Java中的遗留物。

英文:

You could have a look at the Go standard library sources where you can find lot of idiomatic Go code.

You are right: var xs []int differs from the other two variants as it does not "initialize" xs, xs is nil. 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. 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) }.

new cannot be avoided total as it is the only way to create a pointer e.g. to uint32 or the other builtin types. 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"}. Usage of new is not really discouraged, but given the ability of struct literals it just looks like a leftover from Java to me.

答案4

得分: 0

切片

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

我避免使用第三个选项,除非我需要声明一个大小:

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

我避免使用第二个选项,除非我有要包含的值:

xs := []int{9, 8}

映射

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

我避免使用第二个选项,除非我有要包含的值:

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

结构体

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

我避免使用第二个选项,除非我有要包含的值:

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

指针

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

我避免使用第三个选项,除非我有要包含的值:

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

我避免使用第二个选项,除非变量的每次使用都是在“指针模式”下:

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)
英文:

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)

答案5

得分: 0

在这个讨论中,对术语“initialized”的误用很多。保证为nil、0或""的值都是“initialized”,而不是未初始化的。
可能在其中包含随机残留内存内容的值是未初始化的。
C不会初始化内存,Go会。

英文:

There is a lot of misuse of the term initialized in this thread. Values that are guaranteed to be nil, 0, or "" are all initialized, not uninitialized.
Values that may have random leftover memory content in them are uninitialized.
C does not initialize memory, Go does.

huangapple
  • 本文由 发表于 2013年7月12日 07:48:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/17605425.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定