ChatGPT解决这个技术问题 Extra ChatGPT

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

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

复制内置函数将元素从源切片复制到目标切片。 (作为一种特殊情况,它还将字节从字符串复制到字节片。)源和目标可能重叠。 Copy 返回复制的元素数量,它将是 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]

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

谢谢大家,很遗憾我没有注意到切片应该是相同的长度。
不一定相同,但 dst 至少应与您要复制的元素一样大(对于 src 的完整副本,它意味着 len(dst) >= len(src))。
b := append([]int{}, a...)

i
icza

内置 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 包中没有记录,但它记录在 Go Language Specification: Appending to and copying slices 中:

复制的元素数量是 len(src) 和 len(dst) 中的最小值。

编辑:

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

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


总而言之,如果目标切片太小,copy 不包含用于增加目标切片的逻辑,但还有另一个内置函数可以做到这一点:append 虽然在此示例中最好只分配正确大小首先是 slice,当你已经有一个 slice 并且想通过在末尾添加元素来扩大它时,可以使用 append
但是,为什么在复制无界大小的切片时必须创建有界大小的切片?
D
Dave

另一种简单的方法是使用 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 的简写是 append(make([]int, 0, len(arr)), arr...)

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


这里的问题是,在更大的实际示例中,append 将分配多余的内存——除非这个数组稍后通过一些进一步的处理来填充容量——因为它是为重复调用的有效重新分配而设计的。 play.golang.org/p/5_6618xnXn 观察到 cap(x) 增加到 12,而不是 10。现在看看当 1 个值添加到 1048576 个值时会发生什么play.golang.org/p/nz32JPehhl容量跳跃 2048 个槽位到 1050624,只容纳一个附加值。
允许同时解决@j.andrewshusta 提出的问题的在线解决方案的另一个选项是初始化输入切片的容量,如下所示:tmp := append(make([]int, 0, len(arr)), arr...)。在底层,append 函数会在每次调用时检查切片的容量,如果即将超出容量,则分配一个新切片。该解决方案具有避免重复重新分配的额外好处。
仅供参考,使用我的基准测试中的 copy() 大约快 15%。 OTOH copy() 使用起来更复杂,因为这个 Go Playground 说明了 go.dev/play/p/XDGuVU0xfRy — 此处的基准和结果gist.github.com/mikeschinkel/fbeb291b90970581b4428fbc14e4ab19,因为您无法在共享服务器上可靠地进行基准测试。但是,我通常仍会使用 append() 方法,因为它比 copy() 版本更容易理解并且不太可能破坏,除非性能对于给定的用例绝对至关重要。
E
Esze

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() 相比,对于超过 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

append 应该在数组将通过重复调用增加的情况下使用,因为它会乐观地分配多余的容量来预期这一点。在结果数组应创建为精确大小且不再重新分配的情况下,每个输入数组应使用一次副本。 play.golang.org/p/0kviwKmGzx 您没有分享产生这些结果的基准代码,因此我无法确认或否认其有效性,但它忽略了这个更重要的方面。
您的意思是“切片”而不是数组。它们是不同的东西。
i
icza

如果您的切片大小相同,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”:

复制功能支持在不同长度的切片之间复制(它只会复制到较少数量的元素)

通常的例子是:

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

D
Debapriya Biswas

克隆为切片的最佳方法是

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

这种实现有两个优点:

如果 s 为 nil,请确保结果 sClone 为 nil,如果 s 不为 nil,则确保结果不为 nil。即使 T 在另一个包中声明,也无需导入 T 类型的包含包


感谢这个好答案,它比赞成票更值得!
p
peterSO

Go 编程语言规范附加和复制切片函数 copy 将切片元素从源 src 复制到目标 dst 并返回复制的元素数量。两个参数必须具有相同的元素类型 T 并且必须可分配给类型为 []T 的切片。复制的元素数量是 len(src) 和 len(dst) 中的最小值。作为一种特殊情况,copy 还接受一个可分配给类型 []byte 的目标参数,其源参数为字符串类型。这种形式将字符串中的字节复制到字节片中。复制(dst,src []T) int 复制(dst []byte,src 字符串) 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]

x
xilpex

注意:这是@benlemasurier 证明的错误解决方案

这是一种复制切片的方法。我有点晚了,但有一个比@Dave 更简单、更快的答案。 This 是从 @Dave 之类的代码生成的指令。 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]

这是不正确的,如下所示:play.golang.org/p/q3CoEoaid6d。预期的输出应该反映@Dave 的回答:play.golang.org/p/mgdJ4voSlpd
@benlemasurier -- 嗯......看来你是对的!谢谢你让我知道!
S
Sumer

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

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

s
shooma

如果你不关心速度:

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

tmp := slices.Clone(arr)

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