通用的方法来复制切片。

huangapple go评论91阅读模式
英文:

Generic way to duplicate slices

问题

我有一个需要复制切片(以及底层数组的一部分)的需求,这样调用者就不会改变原始数组的元素。我认为我可以编写一个函数来处理特定类型的数组:

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

但是是否有一种通用的方式来创建相同的方法,可能不需要泛型?

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

请注意,上述代码中的问号(?)表示需要根据具体情况替换为适当的类型。

英文:

I have a need to duplicate slices (and part of the underlying array) so a caller won't mutate the original elements of an array. I think I can write a function to do this for arrays of specific types:

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

But is there a way to create the same method generically, perhaps without generics?

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

答案1

得分: 33

你可以编写一个简单的语句来创建一个切片的浅拷贝:

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]

补充说明:

Reflection的常见困难

如果人们刚开始学习Go,他们根本不应该使用反射。

  • Rob

即使对于专家来说,反射也是微妙的。它暴露了一些细节,其理解取决于对语言的基本工作原理的了解,以及在较小程度上,对其实现方式的了解。即使对于经验丰富的Go程序员来说,它也可能令人困惑;对于新手来说,有更重要、更简单的事情要先学习。那些过早学习反射的人会混淆自己,使他们对这些基本原理的理解变得模糊。最好保持一定的距离,直到整个图景变得清晰。

  • 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]: https://groups.google.com/d/topic/golang-nuts/abpF-Ykd0Z0/discussion
英文:

You could write one simple statement to make a shallow copy of a slice,

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

Which is equivalent to,

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

For example,

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

Output:

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

ADDENDUM:

> Common difficulties with reflection
>
> If people are new to Go, they shouldn't be using reflection at all.
>
> -rob
>
> Reflection is subtle even for experts. It exposes details whose
> understanding depends on knowing pretty fundamental things about how
> the language works and, to a lesser extent, how it is implemented. It
> can be bewildering even for experienced Go programmers; for newly
> minted Gophers there are much more important, simpler things to learn
> first. Those who learn reflection too early confuse themselves cloud
> their understanding of those fundamentals. Best to keep it at arm's
> length until the rest of the picture is clear.
>
> -rob

That said,

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

Output:

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]

答案2

得分: 1

Go 1.18

在 Go 1.18 中引入了类型参数,因此可以轻松实现这一点。你可以编写一个通用函数,如下所示:

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

你可以在这个 GoTip playground 中运行这段代码。


实验性的 slices

golang.org/x/exp/slices 包提供了用于切片的通用函数。它可能会在将来被移入标准库中。

我们可以使用 slices.Clone 来实现与之前的 duplicateSlice 函数相同的功能。请注意,这是一个浅拷贝

Clone 返回切片的副本。元素使用赋值进行复制,因此这是一个浅克隆。

package main

import (
	"fmt"
	"golang.org/x/exp/slices"
)

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

	fmt.Println(a2)  // [foo bar]
}

GoTip playground: https://gotipplay.golang.org/p/-W3_I0eYLdF


反射,在 Go 1.18 之前

在 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.Interface()
}

请注意,使用 reflect 包会比当前 接受的答案 中的方法慢得多。将此处提供的代码视为一个参考的人为示例。

Playground 链接:https://play.golang.org/p/vZ1aQOFTLmU

英文:

Go 1.18

With the introduction of type parameters in Go 1.18 this is trivial to accomplish. You can write a generic function like this:

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

And use it as such:

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

You can run this code in this GoTip playground.

<hr>

Experimental slices package

The package golang.org/x/exp/slices provides generic functions for slices. It will probably be moved into the standard lib <strike>in Go 1.19.</strike> in the future.

We can use slices.Clone to accomplish the same as with the previous duplicateSlice function. Note that this is a shallow copy:

> Clone returns a copy of the slice. The elements are copied using assignment, so this is a shallow clone.

package main
import (
&quot;fmt&quot;
&quot;golang.org/x/exp/slices&quot;
)
func main() {
a := []string{&quot;foo&quot;, &quot;bar&quot;}
a2 := slices.Clone(a)
fmt.Println(a2)  // [foo bar]
}

GoTip playground: https://gotipplay.golang.org/p/-W3_I0eYLdF

<hr>

Reflection, pre-Go 1.18

Here's also an example of how to use reflect.Copy to accomplish the same thing before Go 1.18:

package main
import (
&quot;fmt&quot;
&quot;reflect&quot;
)
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(&quot;not a slice!&quot;)
}
v := reflect.ValueOf(src)
target := reflect.MakeSlice(t, v.Cap(), v.Len())
reflect.Copy(target, v)
return target.Interface()
}

Keep in mind that using the reflect package will be much slower than using the approach in the currently accepted answer. Consider the code presented here as just a contrived example for reference.

Link to playground: https://play.golang.org/p/vZ1aQOFTLmU

答案3

得分: 0

你可以使用reflect包中的reflect.Copy函数来对任何类型进行复制操作。具体的文档可以参考:http://golang.org/pkg/reflect/#Copy

英文:

You can do a copy on any type by using the reflect package, specifically reflect.Copy: http://golang.org/pkg/reflect/#Copy

huangapple
  • 本文由 发表于 2014年10月18日 04:42:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/26433156.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定