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


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

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

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

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

var f Foo    
f := Foo{}

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

有了指针,我听到了 new 应该避免。这是一个很好的建议,如果是这样,那将被视为有效的用法 new

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


8930
2017-07-11 23:48


起源



答案:


声明变量时,在哪里 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,然后复制)。有关更多信息,请参阅 有效围棋:使用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个字节?)。


8
2017-07-12 19:03





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

你是对的: var xs []int 与其他两个变体不同,因为它不“初始化”xs,xs为零。而其他两个真正构成一个切片。 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到我的遗留物。


4
2017-07-12 00:20





在与Google IO的Go团队进行炉边聊天期间,观众中的某个人向Go团队询问了他们希望从该语言中获取的内容。

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

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


4
2017-07-12 08:39



+1并没有真正回答这个问题,但我确实同意 - 很高兴知道Pike自己这么说。这是我在GoLang找到的一个弱点:太多的方式来宣称并且不清楚它们各自的利弊和适当性 - 有时给我一种“不完全”的感觉。 - Vector


答案:


声明变量时,在哪里 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,然后复制)。有关更多信息,请参阅 有效围棋:使用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个字节?)。


8
2017-07-12 19:03





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

你是对的: var xs []int 与其他两个变体不同,因为它不“初始化”xs,xs为零。而其他两个真正构成一个切片。 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到我的遗留物。


4
2017-07-12 00:20





在与Google IO的Go团队进行炉边聊天期间,观众中的某个人向Go团队询问了他们希望从该语言中获取的内容。

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

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


4
2017-07-12 08:39



+1并没有真正回答这个问题,但我确实同意 - 很高兴知道Pike自己这么说。这是我在GoLang找到的一个弱点:太多的方式来宣称并且不清楚它们各自的利弊和适当性 - 有时给我一种“不完全”的感觉。 - Vector