复制Go切片的通用方法?

时间:2014-10-17 20:42:09

标签: generics go

初学者来自程序员。我需要复制切片(以及底层数组的一部分),以便调用者不会改变数组的原始元素。我想我可以编写一个函数来为特定类型的数组执行此操作:

func duplicateSliceOfSomeType(sliceOfSomeType []SomeType) []SomeType {
    dulicate := make([]SomeType, len(sliceOfSomeType))
    copy(duplicate, sliceOfSomeType)
    return duplicate
}

但有没有办法一般地创建相同的方法,也许没有泛型?

func duplicateSlice(slice []?) []?{
    duplicate := make([]?, len(slice))
    copy(duplicate, slice)
    return duplicate
}

4 个答案:

答案 0 :(得分:26)

您可以编写一个简单的语句来制作切片的浅表副本,

b := append([]T(nil), a...)

相当于,

b := make([]T, len(a))
copy(b, a)

例如,

package main

import "fmt"

type T int

func main() {
    a := []T{4, 2}

    b := append([]T(nil), a...)

    fmt.Println(&a[0], a, &b[0], b)
    b[0] = 9
    fmt.Println(&a[0], a, &b[0], b)
}

输出:

0x10328000 [4 2] 0x10328020 [4 2]
0x10328000 [4 2] 0x10328020 [9 2]

附录:

  

Common difficulties with reflection

     

如果人们不熟悉Go,他们就不应该使用反射。

     

-rob

     

即使是专家,反思也很微妙。它暴露了其细节   理解取决于了解如何基本的事情   语言有效,在较小程度上,如何实施。它   即使是经验丰富的Go程序员也会感到困惑;新的   铸造Gophers有更重要,更简单的东西需要学习   第一。那些过早学习反思的人会把自己迷惑起来   他们对那些基本面的理解。最好把它放在手臂上   长度,直到图片的其余部分清晰。

     

-Rob

那就是说,

package main

import (
    "fmt"
    "reflect"
)

func CopySlice(s interface{}) interface{} {
    t, v := reflect.TypeOf(s), reflect.ValueOf(s)
    c := reflect.MakeSlice(t, v.Len(), v.Len())
    reflect.Copy(c, v)
    return c.Interface()
}

type T int

func main() {

    {
        // append
        a := []T{4, 2}
        b := append([]T(nil), a...)
        fmt.Println(&a[0], a, &b[0], b)
        b[0] = 9
        fmt.Println(&a[0], a, &b[0], b)
    }

    {
        // make and copy
        a := []T{4, 2}
        b := make([]T, len(a))
        copy(b, a)
        fmt.Println(&a[0], a, &b[0], b)
        b[0] = 9
        fmt.Println(&a[0], a, &b[0], b)
    }

    {
        // reflection
        a := []T{4, 2}
        b := CopySlice(a).([]T)
        fmt.Println(&a[0], a, &b[0], b)
        b[0] = 9
        fmt.Println(&a[0], a, &b[0], b)
    }

}

输出:

0xc20800a200 [4 2] 0xc20800a210 [4 2]
0xc20800a200 [4 2] 0xc20800a210 [9 2]
0xc20800a290 [4 2] 0xc20800a2a0 [4 2]
0xc20800a290 [4 2] 0xc20800a2a0 [9 2]
0xc20800a310 [4 2] 0xc20800a320 [4 2]
0xc20800a310 [4 2] 0xc20800a320 [9 2]

答案 1 :(得分:0)

您可以使用reflect包来复制任何类型的副本,具体为reflect.Copyhttp://golang.org/pkg/reflect/#Copy

答案 2 :(得分:0)

随着 Go 1.18(2022 年初发布的候选版本)中引入类型参数,这将很容易实现。

基于 current proposed specs,您可以编写这样的通用函数:

func duplicateSlice[T any](src []T) []T {
    dup := make([]T, len(src))
    copy(dup, src)
    return dup
}

并使用它:

package main

import (
    "fmt"
)

func duplicateSlice[T any](src []T) []T {
    dup := make([]T, len(src))
    copy(dup, src)
    return dup
}

func main() {
    a := []string{"foo", "bar"}
    a2 := duplicateSlice(a)

    a[0] = "baz"
    fmt.Println(a)  // [baz bar]
    fmt.Println(a2) // [foo bar]

    b := []uint64{8, 12, 30}
    b2 := duplicateSlice(b)

    b[0] = 7
    fmt.Println(b)  // [7 12 30]
    fmt.Println(b2) // [8 12 30]
}

您可以在此 Go2 playground 中运行此代码。

答案 3 :(得分:0)

为了完整起见,为了扩展 Evan's answer,以下是在 Go 1.18 之前如何使用 reflect.Copy 完成相同事情的示例:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    src := []int{1,2,3}
    target := duplicateSlice(src)

    src[0] = 9
    fmt.Println(src)    // [9 2 3]
    fmt.Println(target) // [1 2 3]
}

func duplicateSlice(src interface{}) interface{} {
    t := reflect.TypeOf(src)
    if t.Kind() != reflect.Slice {
        panic("not a slice!")
    }
    v := reflect.ValueOf(src)
    
    target := reflect.MakeSlice(t, v.Cap(), v.Len())
    reflect.Copy(target, v)
    return target
}

请记住,使用 reflect 包将比使用当前 accepted answer 中的方法慢得多。将此处提供的代码视为一个人为的示例以供参考。

游乐场链接:https://play.golang.org/p/8JjU1qGG8Q8