【问题标题】:Initialize struct members with corresponding array elements, when the number of struct members equals the array length当结构成员的数量等于数组长度时,用相应的数组元素初始化结构成员
【发布时间】:2021-02-23 08:15:48
【问题描述】:

我有这样一个结构体和一个数组定义:

type MyStruct struct {
        A int,
        B int,
        C int,
        D int,
        E int,
}

arr := [5]int{1, 2, 3, 4, 5}

有没有一种方法或内置方法可以有效地创建一个新的MyStruct 对象,每个成员按顺序用arr 元素初始化? (比如,A = 1,B = 2,C = 3,D = 4,E = 5)

【问题讨论】:

  • 理想情况下,您应该将数组与数组一起使用。否则你会遇到类型系统。因此,如果可以,请尝试在 MyStruct 中使用数组而不是单个属性。

标签: arrays go struct slice


【解决方案1】:

不,没有。您必须列出 composite literal 中的值:

m := MyStruct{A: arr[0], B: arr[1], C: arr[2], D: arr[3], E: arr[4]}

或者如果你使用无键文字:

m := MyStruct{arr[0], arr[1], arr[2], arr[3], arr[4]}

Go Playground 上试试这些。

您可以使用反射来做到这一点,但使用它(以及使用无键文字)是脆弱的。更改字段或其顺序可能会导致可能被忽视的错误(没有编译时错误)。

【讨论】:

    【解决方案2】:

    首先,我要强调的是,此解决方案的实际生产代码答案是 @izca 的,在您进一步深入兔子洞之前,我将在这里报告:

    m := MyStruct{A: arr[0], B: arr[1], C: arr[2], D: arr[3], E: arr[4]}
    

    但是必须有其他方法可以做到这一点。如果你是 C 程序员,你就会知道,如果你有一个大小相同的结构体和数组,它们是可以被强制转换的,对吧?


    就是这样,使用 unsafe 包进行 C 风格类型转换(Go 中的转换):

    func main() {
        arr := [5]int{1, 2, 3, 4, 5}
        q := (*MyStruct)(unsafe.Pointer(&arr))
        fmt.Printf("%#v\n", q)
    }
    
    type MyStruct struct {
        A int
        B int
        C int
        D int
        E int
    }
    

    Playground

    你真的应该使用这个吗?不。我应该强调这是玩弄的东西,如果没有很好的理由,你不应该真的这样做。包unsafe,从它的名字可以很清楚地推断出来,是不安全的。它不提供任何保证,并且如果使用不当,可能会破坏从垃圾收集器到 cgo 到交叉编译的任何东西。然而,理所当然地认为结构的内存表示保持一致(我强调,这不是spec定义的,因此依赖于实现),这个解决方案将继续工作。

    这里要注意的一件有趣的事情是*qarr 都指向内存中的同一个东西。这意味着你可以做这样有趣的事情:

    arr := [5]int{1, 2, 3, 4, 5}
    q := (*MyStruct)(unsafe.Pointer(&arr))
    fmt.Printf("%#v\n", q) // => &main.MyStruct{A:1, B:2, C:3, D:4, E:5}
    arr[0] = 0xFFFF
    fmt.Printf("%#v\n", q) // => &main.MyStruct{A:65535, B:2, C:3, D:4, E:5}
    

    当然,除了是一个有趣的实验之外,这也意味着通常将引用保留在真实代码中是一个坏主意。所以如果你必须使用不安全的解决方案,你应该按值复制,这​​样你就知道它不会引用原始数组。

    // with the extra asterisk, called indirect,
    // q will be type MyStruct and the example above will fail
    q := *(*MyStruct)(unsafe.Pointer(&arr))
    

    最后,一个更现实的解决方案,但无效,除非您有一个包含许多字段的结构(在这种情况下,您可能应该只使用代码生成,为什么要使用这么大的结构?)

    arr := [5]int{1, 2, 3, 4, 5}
    v := reflect.ValueOf(&MyStruct{}).Elem()
    for i, val := range arr {
        v.Field(i).SetInt(int64(val))
    }
    fmt.Printf("%#v\n",v.Interface().(MyStruct))
    

    Playground

    你真的应该使用这个吗?不是真的 - 但它比使用 unsafe 更好,因为 reflect 库保留了 Go 1 compatibility promise。但是请注意,这将是所有解决方案中最慢的,尤其是当您添加了所有在现实生活中实际需要处理的检查时。如上所述,一般来说,如果您确实有那么多字段可以证明使用反射来缩小代码,您应该首先问自己拥有这么大的结构有什么好处,如果您对此有一个很好的答案,那么真正的解决方案是构建一个代码生成器,将数组复制到结构中。

    (这个答案的重点是表明是的,确实,有一些方法可以真正解决 OP 的问题 - 但也有非常正当的理由不应该使用它们!)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多