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

kq0g1dla  于 2023-06-03  发布在  Go
关注(0)|答案(5)|浏览(403)

我喜欢Go没有给予我一百万种方法来做简单的事情的事实-借用Python的禅宗,“应该有一个-最好只有一个-明显的方法来做。
然而,我不清楚示例化类型的首选/惯用方法。基本类型很简单:

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

但是结构体呢?下列各项是否等同,如果是,哪一项是首选,为什么?

var f Foo    
f := Foo{}

切片呢?我可以使用var xs []intxs := []int{}xs := make([]int),但我认为第一个选项(与结构体相反)与其他选项不同?我想这也适用于Map。
对于指针,我听说应该避免使用new。这是一个好的建议吗?如果是的话,new的有效用法是什么?
我意识到这可能部分是一个风格问题,但在任何情况下,偏好某种特定风格的理由都会有所帮助。

ni65a41a

ni65a41a1#

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

var name T

Go会给你一段未初始化的“归零”内存。
对于基元,这意味着var name int将为0,var name string将为“"。在C it might be zeroed, or might be something unexpected中。Go保证未初始化的变量是类型的零等价物。
在内部,切片、贴图和通道被视为指针。指针零值为nil,意味着它指向nil内存。如果不初始化它,如果尝试对其进行操作,可能会遇到死机。
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?

一个slices length是它开始的项目数。容量是在需要调整大小之前分配的内存(内部,新大小 * 2,然后复制)。有关更多信息,请参阅Effective Go:使用make分配。
new(T)等价于&T{},而不是T{}*new(T)相当于*&T{}
切片:make([]T,0)相当于[]T{}
Map: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字节(64位上为8字节?).

ut6juiuv

ut6juiuv2#

在Google IO与Go团队的炉边谈话中,观众中有人问Go团队他们想从语言中提取什么。
Rob说他希望有更少的方法来声明变量,并提到:
冒号等于覆盖,命名结果参数(https://plus.google.com/+AndrewGerrand/posts/LmnDfgehorU),在for循环中重用的变量令人困惑,特别是对于闭包。但是,语言可能不会有太大的变化。

os8fio9y

os8fio9y3#

你可以看看Go标准库的源代码,在那里你可以找到很多惯用的Go代码。
你说得对:var xs []int与其他两个变量不同,因为它不“初始化”xs,xs为nil。而另外两个实际上构建了一个切片。xs := []int{}是常见的,如果你需要一个零上限的空切片,而make给你更多的选择:长度和容量。另一方面,通常以nil切片开始,并通过追加来填充,如var s []int; for ... { s = append(s, num) }
new不能避免total,因为它是创建指针的唯一方法,例如uint32或其他内置类型。但你是对的,写a := new(A)是非常罕见的,大多数情况下写为a := &A{},因为这可以转换为a := &A{n: 17, whatever: "foo"}new的使用并不真正被禁止,但考虑到结构体文字的能力,它在我看来就像是Java的残余。

9wbgstp7

9wbgstp74#

slice

  1. var xs []int
  2. xs := []int{}
  3. xs := make([]int, 2)
    我避免第三项,除非我需要声明一个大小:
xs := make([]int, 2)
xs[1] = 100

我避免第二项,除非我有值包括:

xs := []int{9, 8}

Map

  1. xs := make(map[string]int)
  2. xs := map[string]int{}
    我避免第二项,除非我有值包括:
xs := map[string]int{"month": 12, "day": 31}

struct

  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)
olhwl3o2

olhwl3o25#

在这个线程中有很多对术语 initialized 的误用。保证为nil、0或“”的值都是 initialized,而不是uninitialized。可能具有随机剩余内存内容的值未初始化。C不会初始化内存,Go * 会。

相关问题