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

时间:2013-07-11 23:48:30

标签: types go declaration instantiation idiomatic

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

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

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

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

var f Foo    
f := Foo{}

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

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

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

4 个答案:

答案 0 :(得分:10)

声明变量时,T属于某种类型:

var name T

Go为您提供了一段未初始化的“归零”记忆。

对于原语,这意味着var name int将为0,而var name string将为“”。在C it might be zeroed, or might be something unexpected。 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,然后复制)。有关详细信息,请参阅Effective Go: Allocation with 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{}在这种情况下更干净,虽然我倾向于使用带有结构的指针,以避免在传递它时复制它。

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

答案 1 :(得分: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中的遗留物。

答案 2 :(得分:4)

在与谷歌IO的Go团队的炉边聊天期间,观众中的某个人向Go团队询问了他们希望从该语言中删除的内容。

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

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

答案 3 :(得分: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)