13

我喜欢 Go 并没有给我一百万种方法来做简单事情的事实——借用 Python 之禅,“应该有一种——最好只有一种——显而易见的方法。”</p>

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

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

但是结构呢?以下是等价的,如果是,哪个是首选的,为什么?

var f Foo    
f := Foo{}

切片呢?我可以做var xs []int, xs := []int{}, or xs := make([]int),但我认为第一个选项(与结构相反)与其他选项不同?我认为这也适用于地图。

有了指针,我听说new应该避免这种情况。这是一个好的建议吗?如果是这样,什么算作 的有效用法new

我意识到这可能部分是风格问题,但在任何情况下,偏爱特定风格的理由都会有所帮助。

4

4 回答 4

12

声明变量时,T某种类型在哪里:

var name T

Go 给你一块未初始化的“归零”内存。

对于原语,这意味着var name int它将是 0,并且var name string将是“”。在C 中,它可能被归零,或者可能是一些意想不到的东西。Go 保证未初始化的变量是类型的零等价物。

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

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?

切片length是它开始的项目数。容量是需要调整大小之前分配的内存(内部,新大小 * 2,然后复制)。有关更多信息,请参阅有效 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"}
    // 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{}. T{}在这种情况下更干净,尽管我倾向于使用带有结构的指针来避免在传递它时制作副本。

要记住的另一件事是, a比 struct[]*struct更小,调整大小更便宜[]struct,假设结构比指针大得多,指针通常为 4 - 8 个字节(64 位上为 8 个字节?)。

于 2013-07-12T19:03:08.723 回答
5

在 Google IO 与 Go 团队的炉边聊天期间,听众中有人问 Go 团队他们想从语言中得到什么。

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

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

于 2013-07-12T08:39:35.750 回答
4

您可以查看 Go 标准库源代码,从中可以找到很多惯用的 Go 代码。

你是对的:var xs []int与其他两个变体不同,因为它不会“初始化”xs,xs 是 nil。而另外两个确实构造了一个切片。xs := []int{}如果您需要一个零上限的空切片,这很常见,同时make为您提供更多选择:长度和容量。另一方面,通常从一个 nil 切片开始,然后通过添加如 in 来填充var s []int; for ... { s = append(s, num) }

new完全不能避免,因为它是创建指针的唯一方法,例如指向 uint32 或其他内置类型。但你是对的,写作a := new(A)很少见,而且主要是a := &A{}因为它可以变成a := &A{n: 17, whatever: "foo"}. 的使用new并没有真正气馁,但考虑到结构文字的能力,它对我来说就像是 Java 的遗留物。

于 2013-07-12T00:20:33.217 回答
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)
于 2021-04-17T14:05:43.890 回答