我喜欢这样一个事实:Go并没有给我百万种方法来做简单的事情 - 借用Python的Zen,“应该有一个 - 最好只有一个 - 显而易见的方法。”
但是,我不清楚实例化类型的首选/惯用方法。基本类型很简单:
n := 0
t := 1.5
str := "Hello"
但结构呢?以下是等效的,如果是,哪个是首选的,为什么?
var f Foo
f := Foo{}
切片怎么样?我可以var xs []int
,xs := []int{}
或xs := make([]int)
,但我认为第一个选项(与结构相对)与其他选项不同?我认为这也适用于地图。
有了指针,我听说应该避免使用new
。这是一个很好的建议,如果是,那么什么才算作new
的有效用法?
我意识到这可能部分是一种风格问题,但在任何情况下,选择特定风格的理由都会有所帮助。
答案 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)
var xs []int
xs := []int{}
xs := make([]int, 2)
我避免第三项,除非我需要声明尺寸:
xs := make([]int, 2)
xs[1] = 100
我避免第二项,除非我有值要包括:
xs := []int{9, 8}
xs := make(map[string]int)
xs := map[string]int{}
我避免第二项,除非我有值要包括:
xs := map[string]int{"month": 12, "day": 31}
var f Foo
f := Foo{}
我避免第二项,除非我有值要包括:
f := Foo{31}
f := Foo{Day: 31}
var f Foo; &f
f := new(Foo)
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)