【问题标题】:Implications of defining a struct inside a function vs outside?在函数内部与外部定义结构的含义?
【发布时间】:2017-02-02 18:52:10
【问题描述】:

在函数内部定义struct 与在外部定义它是否有任何影响(GC 流失、性能或其他)?例如:

type Outside struct {
  Foo string `json:"foo"`
}

func SomeFunc(b []byte) error {
  outside := Outside{}

  if err := json.NewDecoder(b).Decode(&outside); err != nil {
    return err
  }

  ...
}

对比

func SomeFunc(b []byte) error {

  type inside struct {
    Foo string `json:"foo"`
  }

  if err := json.NewDecoder(b).Decode(&inside); err != nil {
    return err
  }

  ...
}

会不会有任何一种情况会出现优先于另一个的情况?

【问题讨论】:

    标签: go


    【解决方案1】:

    对我来说,在函数中定义的类型的主要缺点是您无法在该类型上定义方法。

    看这个例子https://play.golang.org/p/cgH01cRwDv6

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        type MyType struct {
            Name string
        }
    
        // You cannot define a method on your type
        // defined in a function, can you?
    
        func (m MyType) String() string {
          return m.Name
        }
    
        m := MyType{Name: "Hello, World!"}
        fmt.Println(m)
    }
    

    上面的示例将失败并出现错误prog.go:15:27: expected ';', found 'IDENT' string (and 1 more errors)

    【讨论】:

      【解决方案2】:

      没有性能差异——它只是范围的差异(即可以看到类型定义的地方)。如果您只需要单个函数中的类型,则可以在其中定义它。

      正如其他人所指出的,如果您在包级别(即函数外部)定义一个名称以大写字母开头的类型,它将被导出(即在包外部可见)。如果名称不以大写字母开头,则仅在包中可见。

      【讨论】:

        【解决方案3】:

        我的理解是,区别仅在于可访问性。

        • 大写字母开头定义的结构将是可导出的,这意味着它可以从其他包中访问。
        • 小写字母开头定义的结构可以从同一包内的任何地方访问,但不能从外部访问。
        • inline 函数中定义的结构只能由该函数访问/初始化。

        【讨论】:

          【解决方案4】:

          我曾经在一个函数中定义了一个结构,用于将 JSON 字节数组 ([]byte) 编组到结构实例中,并从实例中提取一条消息。

          显然不需要定义结构。我可以通过将 JSON 字节数组编组为 interface{} 来提取消息,然后递归地强制转换以获得所需的消息。

          通过定义结构,消息的提取变得非常容易:)

              var errDetail struct {
                  Message string `json:"message"`
                  Success bool   `json:"success"`
              }
              
              json.Unmarshal(*bytes, &errDetail)
              
              if errDetail.Message == "" {
                  fmt.Println("error message is not present")
                  return nil
              }
              return errDetail.Message
          

          【讨论】:

            【解决方案5】:

            正如其他人提到的那样,它是关于限制变量范围的。如果要在函数中使用结构体,也可以使用匿名结构体。

            package main
            
            import (
                "fmt"
            )
            
            func main() {
            
                m := struct {
                    greeting string
                    name     string
                }{
                    greeting: "hello",
                    name:     "world",
                }
            
                fmt.Printf("%v %v\n", m.greeting, m.name)
            }
            

            如果您只打算在函数内使用结构,您可以定义结构的字段并立即为它们分配值。

            【讨论】:

              【解决方案6】:

              范围不同,可以查看Golang规范here

              最相关的部分是:

              Go 的词法范围使用块:

              表示在顶层(任何函数之外)声明的常量、类型、变量或函数(但不是方法)的标识符的范围是包块。

              在函数内部声明的类型标识符的范围从 TypeSpec 中的标识符开始,到最里面的包含块的末尾结束。

              如果你使用go tool compile -S -N hello.go检查生成的汇编代码,你可以发现包级别定义的类型名称和函数内部定义的类型名称是不同的。

              包级别

              package main
              
              import (
                  "fmt"
              )
              
              type Point struct {
                  X, Y int
              }
              
              func main() {
                  fmt.Printf("%v\n", Point{X: 1, Y: 2})
              }
              

              然后尝试编译,找到这一行:type."".Point SRODATA size=144

              内部函数

              package main
              
              import (
                  "fmt"
              )
              
              
              func main() {
                  type Point struct {
                      X, Y int
                  }
                  fmt.Printf("%v\n", Point{X: 1, Y: 2})
              }
              

              然后尝试找到这一行:type.*"".Point·1 SRODATA size=56

              也就是说,它们只是编译后得到了不同的名称。

              【讨论】:

                【解决方案7】:

                对于 Konrad Kleine 的问题,您仍然可以使用类似这样的解决方法来解决 https://play.golang.org/p/50yv66LUNRt

                package main
                
                import (
                    "fmt"
                )
                
                func main() {
                    type MyType struct {
                        Name string
                        String func() string
                    }
                    InitMyType := func(m *MyType) {
                        m.String = func() string {
                            return m.Name
                        }
                        return 
                    }
                    
                    m := MyType{Name: "Hello, World!"}
                    initMyType(&m)
                    
                    fmt.Println(m.String())
                }
                

                我同意区别只是可访问性。

                但这还是很有用的,特别是你想要的只是一个临时结构,或者你做单元测试的时候,有很多类似的结构,比如args,一个包里的测试用例,你就不用费心去命名它们了一个接一个。

                【讨论】:

                  猜你喜欢
                  • 2012-02-18
                  • 2021-08-20
                  • 2020-08-15
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-10-07
                  • 1970-01-01
                  • 2019-10-05
                  • 2021-12-24
                  相关资源
                  最近更新 更多