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


121

我需要在Go中制作切片的副本,并阅读文档,这里有一个复制功能供我使用。

内置复制功能将元素从源切片复制到目标切片。(在特殊情况下,它还会将字节从字符串复制到字节的一部分。)源和目标可能会重叠。复制返回复制的元素数量,该数量将是len(src)和len(dst)的最小值。

但是当我这样做时:

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

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

[]
[1 2 3]

你可以在操场上检查一下。那为什么不能复制切片?


谢谢大家,我没有注意到切片的长度应该是相同的,这让我很难过。
2015年

1
不一定相同,但dst至少应与要复制的元素一样大(完整复制src意味着len(dst) >= len(src))。
icza 2015年

2
b := append([]int{}, a...)
rocketspacer

Answers:


209

内置copy(dst, src)副本min(len(dst), len(src))元素。

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

尝试tmp := make([]int, len(arr))前往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软件包中,而是记录在Go语言规范中:附加和复制切片

复制的元素的数目是最小len(src)len(dst)

编辑:

最后,的文档copy()已更新,现在包含以下事实:将复制源和目标的最小长度:

复制返回复制的元素数,该数量将是len(src)和len(dst)的最小值


2
总而言之,copy如果目标切片太小,则不包含用于增大目标切片的逻辑,但是还有另一个内置函数可以执行append 此操作:虽然在此示例中,最好首先分配合适大小的切片,append当您已经有一个切片并想通过在其末尾添加元素来扩大切片时可以使用。
thomasrutter

1
但是,为什么在复制无界大小切片时必须创建有界大小切片?
亚历克斯

24

另一种简单的方法是使用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]

所以复制数组的简写arrappend([]int(nil), arr...)

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


8
这里的问题是,在更大的实际示例中,append将分配过多的内存-除非稍后通过进一步处理将该数组填充为容量,因为它是为在重复调用上进行有效的重新分配而设计的。play.golang.org/p/5_6618xnXn观察到cap(x)增加到12,而不是10。现在看看将1值添加到1048576值中会发生什么情况play.golang.org/p/nz32JPehhl容量跳至2048个插槽1050624,仅容纳一个附加值。
j。安德鲁·舒斯塔(Andrew shusta)2017年

12

如果您的切片大小相同,则可以

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]

从“ 切片:用法和内部原理 ”中:

复制功能支持在不同长度的切片之间进行复制(它最多只能复制较少数量的元素

通常的示例是:

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

10

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 slice中。

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]

与distribution + copy()相比,对于超过1,000个元素,请使用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

1
在通过重复调用增加数组的情况下,应该使用append,因为它会在预期的情况下乐观地分配过多的容量。如果应将结果数组创建为精确大小,并且不能重新分配,则每个输入数组应使用一次copy。play.golang.org/p/0kviwKmGzx您没有共享产生这些结果的基准代码,因此我无法确认或否认其有效性,但它忽略了这一更重要的方面。
j。安德鲁·舒斯塔(Andrew Shusta)2017年

1
你的意思是“切片”不是数组。他们是不同的东西。
伊南克·古姆斯

2

Go编程语言规范

附加并复制切片

该函数复制将切片元素从源src复制到目标dst,并返回复制的元素数。这两个参数必须具有相同的元素类型T,并且必须可分配给类型[] T的切片。复制的元素数是len(src)和len(dst)的最小值。在特殊情况下,复制还接受可分配给类型为[] byte且源参数为字符串类型的目标参数。这种形式将字节从字符串复制到字节片中。

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]

0

这是一种复制切片的方法。我来晚了一点,但是有一个比@Dave更快,更简单的答案。是从类似@Dave的代码生成的指令。这些是我的指令。如您所见,说明很少。它所要做的只是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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.