Go语言 在函数内部和外部定义结构体的含义?

xqk2d5yq  于 2023-02-17  发布在  Go
关注(0)|答案(7)|浏览(196)

在函数内部定义struct与在函数外部定义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
  }

  ...
}

是否会有一种情况优于另一种情况?

7uzetpgm

7uzetpgm1#

对我来说,在函数中定义类型的主要缺点是不能在该类型上定义方法。
请参见以下示例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)

tgabmvqs

tgabmvqs2#

没有性能上的差异-,只是作用域的差异(即类型定义在哪里可以看到),如果你只需要在一个函数中定义类型,那么在那里定义就可以了。
正如其他人所指出的,如果你在包层(即函数之外)定义一个类型,其名称以大写字母开头,那么它将被导出(即在包之外可见);如果名称不是以大写字母开头,那么它将只在包之内可见。

1l5u6lss

1l5u6lss3#

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

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

wfsdck304#

我曾经在一个函数中定义了一个struct,用于将JSON字节数组([]byte)编组到struct示例中,并从示例中提取消息。
显然,不需要定义结构,我可以通过将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
ac1kyiln

ac1kyiln5#

正如其他人提到的,它的所有内容都是关于限制变量的作用域。如果你打算在函数中使用结构体,你也可以使用匿名结构体。

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

如果你只打算在函数内部使用结构体,你可以定义结构体的字段,并立即给它们赋值。

pcrecxhr

pcrecxhr6#

范围不同,您可以在Golang spec中查看:
最相关的部分是:
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
比的意思是,他们只是得到不同的名称后,编译。

z9smfwbn

z9smfwbn7#

对于KonradKleine的问题,您仍然可以使用类似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,test case,你就不需要麻烦去一一命名它们了。

相关问题