为什么我不能用 `copy()` 复制切片?

Why can't I duplicate a slice with `copy()`?

我需要在 Go 中复制一个切片并阅读文档,其中有一个 copy 函数供我使用。

The copy built-in function copies elements from a source slice into a destination slice. (As a special case, it also will copy bytes from a string to a slice of bytes.) The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst).

但是当我这样做时:

arr := []int{1, 2, 3}
tmp := []int{}
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)

我的tmp和以前一样是空的(我什至尝试使用arr, tmp):

[]
[1 2 3]

您可以随时查看 playground。那么为什么我不能复制一个切片呢?

如果你的切片大小相同,it would work:

arr := []int{1, 2, 3}
tmp := []int{0, 0, 0}
i := copy(tmp, arr)
fmt.Println(i)
fmt.Println(tmp)
fmt.Println(arr)

会给:

3
[1 2 3]
[1 2 3]

来自“Go Slices: usage and internals”:

The copy function supports copying between slices of different lengths (it will copy only up to the smaller number of elements)

通常的例子是:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

The Go Programming Language Specification

Appending to and copying slices

The function copy copies slice elements from a source src to a destination dst and returns the number of elements copied. Both arguments must have identical element type T and must be assignable to a slice of type []T. The number of elements copied is the minimum of len(src) and len(dst). As a special case, copy also accepts a destination argument assignable to type []byte with a source argument of a string type. This form copies the bytes from the string into the byte slice.

copy(dst, src []T) int
copy(dst []byte, src string) int

tmp 需要足够的空间容纳 arr。例如,

package main

import "fmt"

func main() {
    arr := []int{1, 2, 3}
    tmp := make([]int, len(arr))
    copy(tmp, arr)
    fmt.Println(tmp)
    fmt.Println(arr)
}

输出:

[1 2 3]
[1 2 3]

内置 copy(dst, src) 复制 min(len(dst), len(src)) 个元素。

因此,如果您的 dst 为空 (len(dst) == 0),则不会复制任何内容。

尝试 tmp := make([]int, len(arr)) (Go Playground):

arr := []int{1, 2, 3}
tmp := make([]int, len(arr))
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)

输出(如预期):

[1 2 3]
[1 2 3]

不幸的是,builtin package, but it is documented in the Go Language Specification: Appending to and copying slices:

中没有记录

The number of elements copied is the minimum of len(src) and len(dst).

编辑:

终于更新了 copy() 的文档,它现在包含将复制源和目标的最小长度的事实:

Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst).

另一种简单的方法是使用 append,它将在进程中分配切片。

arr := []int{1, 2, 3}
tmp := append([]int(nil), arr...)  // Notice the ... splat
fmt.Println(tmp)
fmt.Println(arr)

输出(如预期):

[1 2 3]
[1 2 3]

正如下面的评论中所指出的,append 如果开始时切片的大小不正确,则可能会分配过多的内存。一个很好的解决方案是预分配正确容量的一部分,如下所示:

tmp := append(make([]int, 0, len(arr)), arr...)

所以用于复制数组 arr 的 shorthand 将是 append(make([]int, 0, len(arr)), arr...)

https://play.golang.org/p/xwevI1chGrd

copy()运行的dst和src的长度最短,所以你必须将dst初始化为你想要的长度。

A := []int{1, 2, 3}
B := make([]int, 3)
copy(B, A)
C := make([]int, 2)
copy(C, A)
fmt.Println(A, B, C)

输出:

[1 2 3] [1 2 3] [1 2]

您可以使用 append() 将一行中的所有元素初始化并复制到 nil 切片。

x := append([]T{}, []...)

示例:

A := []int{1, 2, 3}
B := append([]int{}, A...)
C := append([]int{}, A[:2]...)
fmt.Println(A, B, C)    

输出:

[1 2 3] [1 2 3] [1 2]

对比allocation+copy(),大于1000个元素,使用append。实际上低于 1,000 的差异可能会被忽略,除非您有很多切片,否则请根据经验进行操作。

BenchmarkCopy1-4                50000000            27.0 ns/op
BenchmarkCopy10-4               30000000            53.3 ns/op
BenchmarkCopy100-4              10000000           229 ns/op
BenchmarkCopy1000-4              1000000          1942 ns/op
BenchmarkCopy10000-4              100000         18009 ns/op
BenchmarkCopy100000-4              10000        220113 ns/op
BenchmarkCopy1000000-4              1000       2028157 ns/op
BenchmarkCopy10000000-4              100      15323924 ns/op
BenchmarkCopy100000000-4               1    1200488116 ns/op
BenchmarkAppend1-4              50000000            34.2 ns/op
BenchmarkAppend10-4             20000000            60.0 ns/op
BenchmarkAppend100-4             5000000           240 ns/op
BenchmarkAppend1000-4            1000000          1832 ns/op
BenchmarkAppend10000-4            100000         13378 ns/op
BenchmarkAppend100000-4            10000        142397 ns/op
BenchmarkAppend1000000-4            2000       1053891 ns/op
BenchmarkAppend10000000-4            200       9500541 ns/op
BenchmarkAppend100000000-4            20     176361861 ns/op

NOTE: This is an incorrect solution as @benlemasurier proved

这是复制切片的一种方法。我来晚了一点,但有一个比@Dave 更简单、更快的答案。 This are the instructions generated from a code like @Dave's. These是我生成的指令。如您所见,指令要少得多。它所做的只是复制切片的 append(slice)。此代码:

package main

import "fmt"

func main() {
    var foo = []int{1, 2, 3, 4, 5}
    fmt.Println("foo:", foo)
    var bar = append(foo)
    fmt.Println("bar:", bar)
    bar = append(bar, 6)
    fmt.Println("foo after:", foo)
    fmt.Println("bar after:", bar)
}

输出这个:

foo: [1 2 3 4 5]
bar: [1 2 3 4 5]
foo after: [1 2 3 4 5]
bar after: [1 2 3 4 5 6]

克隆为切片的最佳方法是

sClone = append(s[:0:0], s...)

这个实现有两个优点:

  1. 如果 s 为 nil 且不为 nil,请确保结果 sClone 为 nil 如果 s 不为零。

  2. 即使在中声明了T,也无需导入类型T的包含包 另一个包裹

甜美、简单、高效、无需注意长度、无内存重叠、不同副本

slice2 := append([]int{}, slice1...)

如果你不关心速度:

import "golang.org/x/exp/slices"

tmp := slices.Clone(arr)

使用 Go 1.18 和泛型,现在可以使用包 "golang.org/x/exp/slices" 中的 slices.Clone 复制任何切片。 Playground