为什么 append 修改传递的切片

Jam*_*May 11 go

我如何遍历切片并将切片传递到当前元素之外的某个地方?正如我们在文档中看到的那样,似乎append()函数修改了底层切片。但无论如何我仍然不知道如何达到这一点。

func main() {
    args := []string{ "2", "3", "8" }

    for i, _ := range args {
        fmt.Println(append(args[:i], args[i+1:]...)) // or pass to function
    }

    fmt.Println(args)
}
Run Code Online (Sandbox Code Playgroud)

结果:

[3 8]
[3 8]
[3 8]
[3 8 8] // it is args now
Run Code Online (Sandbox Code Playgroud)

我的期望:

 [3 8]
 [2 8]
 [2 3]
Run Code Online (Sandbox Code Playgroud)

我已经看到这个为什么 append() 修改提供的切片?(见示例)

但是切片的容量是多少对我来说是秘密,我不明白为什么我超过了它。

Mar*_*ing 8

性能是很大的原因。创建一个新切片并将所有元素复制到它的成本很高,因此切片代码不会无缘无故地复制。但是,如果超出了切片的容量,它会通过复制底层切片以适当的量增长。这意味着返回的切片append可能与您传入的切片不同。

首选的使用方式是:

args = append(args, newarg)
Run Code Online (Sandbox Code Playgroud)

如果您采用子切片,容量保持不变,但您对切片的看法会发生变化。这意味着丢失的元素仍然存在,但在新切片的边界之外。

这解释了代码的奇怪输出。您append每次都打印结果但不存储该结果,这意味着args与您打印的结果不同。

初始args切片有 3 个元素大。对于每个索引i- 也就是说 for 0, 1and 2- 你取一个子切片args[:i]并将数组剩余部分的所有元素附加args[i+1:]到它。这意味着对于:

i    args[:i]     args[i+1]...   Result         args
0    {}           {"3", "8"}     {"3", "8"}     {"3", "8", "8"}
1    {"3"}        {"8"}          {"3", "8"}     {"3", "8", "8"}
2    {"3", "8"}   {}             {"3", "8"}     {"3", "8", "8"}
Run Code Online (Sandbox Code Playgroud)

tl;dr 你应该总是保存结果append,如果你想制作一个副本以便你可以改变它,那么你自己制作一个副本。


Ben*_*ish 5

Append 总是尝试修改底层数组。

让我们看一下循环的第一次执行

append(args[:0], args[0+1:]...)
Run Code Online (Sandbox Code Playgroud)

这样做是将切片 {3,8} 附加到切片 {},因为 args[:0] 为您提供了一个在数组开头结束的空切片。这就是为什么你的数组作为 [3 8 8] 出现的原因,因为 3 8 被附加到数组中。在 wiki 上阅读有关此内容的更多信息。

您可以使用 make 设置默认容量,即

args := make([]string, 0, CAPACITY)
Run Code Online (Sandbox Code Playgroud)

您还可以检查切片的容量

a := []int{1,2,3}
fmt.Println(cap(a))
>>> 3
Run Code Online (Sandbox Code Playgroud)

最后,如果您不想像 Elwinar 的回答那样每次都重新复制数组,我建议将两个切片 a[:i] 和 a[i+1:] 传递给函数。


Elw*_*nar 2

您可以将切片想象为一个由固定大小的数组和其中元素数量的计数器组成的结构。切片的容量是底层数组的大小,而切片的长度是计数器。

\n\n

Append 是这样定义的:( func append(slice []Type, elems ...Type) []Typegodoc ),这本质上意味着您将把elem可变参数附加到slice参数中。如果len(elems) + len(slice) > cap(slice),则需要将底层阵列更改为更大的阵列(具有更大的容量)),这意味着(在 go 中)一个新的切片(因此返回参数)。

\n\n

就您而言,您没有超出切片的容量。您刚刚修改了其内容。

\n\n

一个简单的(尽管有点难看)技巧是将两个追加嵌套到一个空切片中:

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    args := []string{ "2", "3", "8" }\n\n    for i, _ := range args {\n        fmt.Println(append(append([]string{}, args[:i]...), args[i+1:]...)) \n    }\n\n    fmt.Println(args)\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者,如果您想将切片的副本传递给方法(然后执行您想要的操作),您可以使用copy\xe2\x80\xa6

\n