【问题标题】:Preferred way to instantiate types in Go在 Go 中实例化类型的首选方式
【发布时间】:2023-03-08 09:56:02
【问题描述】:

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

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

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

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

var f Foo    
f := Foo{}

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

通过指针,我听说应该避免使用new。这是一个好的建议吗?如果是这样,new 的有效用法是什么?

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

【问题讨论】:

    标签: types go declaration instantiation idioms


    【解决方案1】:

    您可以查看 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】:

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

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

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

      【讨论】:

      • +1 并没有真正回答这个问题,但我确实同意 - 很高兴知道派克本人这么说。这是我在 GoLang 中发现的一个弱点:声明的方式太多,但并不清楚它们各自的优缺点和适当性——有时给我一种“还没有完成”的感觉。
      【解决方案3】:

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

      var name T
      

      Go 为您提供了一块未初始化的“归零”内存。

      对于原语,这意味着var name int 将是 0,而var name string 将是“”。在C it might be zeroed, or might be something unexpected。 Go 保证未初始化的变量是类型的零等价物。

      内部切片、映射和通道被视为指针。指针零值为零,这意味着它指向零内存。不初始化的话,尝试操作可能会遇到panic。

      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 个字节(64 位上的 8 个字节?)。

      【讨论】:

        【解决方案4】:

        切片

        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)
        

        【讨论】:

          猜你喜欢
          • 2018-06-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-14
          • 1970-01-01
          • 2011-11-07
          • 2023-03-19
          • 1970-01-01
          相关资源
          最近更新 更多