为什么追加修改传递切片

时间:2016-03-10 15:22:27

标签: 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)
}

结果:

[3 8]
[3 8]
[3 8]
[3 8 8] // it is args now

我的期望:

 [3 8]
 [2 8]
 [2 3]

我已经看到了这个Why does append() modify the provided slice? (See example)

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

5 个答案:

答案 0 :(得分:2)

追加始终尝试修改基础数组。

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

append(args[:0], args[0+1:]...)

这样做是将切片{3,8}附加到切片{},因为args [:0]会给你一个空切片,它以数组的开头结束。这就是为什么你的阵列出现为[3 8 8]因为3 8被附加到数组。 详细了解此on the wiki

您可以使用make ie。

设置默认容量
args := make([]string, 0, CAPACITY)

您还可以检查切片的容量

a := []int{1,2,3}
fmt.Println(cap(a))
>>> 3

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

答案 1 :(得分:2)

表现是最重要的原因。创建一个新切片并将所有元素复制到它上面是很昂贵的,因此切片代码不会在没有充分理由的情况下复制。但是,如果超过切片的容量,则通过复制基础切片,它会增长适当的量。这意味着从append返回的切片可能与您传入的切片不同。

首选使用方法是:

args = append(args, newarg)

如果您采用子切片,则容量保持不变,但您对切片的视图会发生变化。这意味着缺少的元素仍然存在,但超出了新切片的范围。

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

最初的args切片是3个元素。对于每个索引i - 也就是012 - 你需要一个子标记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"}

tl; dr 您应始终保存append的结果,如果您想制作副本以便进行更改,请自行制作副本。

答案 2 :(得分:1)

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

追加定义方式为:func append(slice []Type, elems ...Type) []Typegodoc),这实际上意味着您将elem可变参数附加到slice参数。如果len(elems) + len(slice) > cap(slice),那么需要更换更大的数组(具有更大的容量)的udnerlying数组,这意味着(在go中)一个新切片(因此返回参数)。 / p>

在您的情况下,您没有超过切片的容量。你刚修改了它的内容。

一个简单的(虽然有点丑陋)技巧就是将两个追加嵌套到一个空切片中:

package main

import "fmt"

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

    for i, _ := range args {
        fmt.Println(append(append([]string{}, args[:i]...), args[i+1:]...)) 
    }

    fmt.Println(args)
}

或者,如果您想将切片的副本传递给方法(并在此之后执行您想要的操作),则可以使用copy函数...

答案 3 :(得分:1)

  

The Go Programming Language Specification

     

Appending to and copying slices

     

内置函数在公共切片中追加和复制辅助   操作。对于这两个函数,结果与是否无关   参数引用的内存重叠。

     

可变参数函数append将零或更多值x附加到s   type S,必须是切片类型,并返回结果切片,   也是S型。值x被传递给类型为...... T的参数   其中T是S的元素类型和相应的参数传递   规则适用。作为特例,append也接受第一个参数   可以使用字符串类型的第二个参数赋值给[]字节   后跟....这个表单附加字符串的字节。

append(s S, x ...T) S  // T is the element type of S
     

如果s的容量不足以容纳附加值,   append分配一个适合的新的,足够大的底层数组   现有的切片元素和附加值。除此以外,   append重新使用底层数组。

s0 := []int{0, 0}
s1 := append(s0, 2)                // append a single element     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append multiple elements    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append a slice              s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append overlapping slice    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

var t []interface{}
t = append(t, 42, 3.1415, "foo")   //                             t == []interface{}{42, 3.1415, "foo"}

var b []byte
b = append(b, "bar"...)            // append string contents      b == []byte{'b', 'a', 'r' }
     

函数副本将切片元素从源src复制到   destination dst并返回复制的元素数。都   参数必须具有相同的元素类型T并且必须可分配给   一片[] T型。复制的元素数量是最小值   len(src)和len(dst)。作为一个特例,副本也接受一个   目标参数可赋值为带有source参数的type [] byte   字符串类型。此表单将字符串中的字节复制到   字节切片。

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

示例:

var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:])            // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:])            // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!")  // n3 == 5, b == []byte("Hello")

请勿使用追加来覆盖您的输入。为输出使用单独的变量(函数参数)。例如,

package main

import "fmt"

func main() {
    args := []string{"2", "3", "8"}
    fmt.Println(args)
    funcArg := make([]string, len(args)-1)
    for i := range args {
        copy(funcArg, args[:i])
        copy(funcArg[i:], args[i+1:])
        fmt.Println(funcArg)
    }
    fmt.Println(args)
}

输出:

[2 3 8]
[3 8]
[2 8]
[2 3]
[2 3 8]

答案 4 :(得分:0)

现有答案充分解释了 OP 观察到的行为并提供了可行的解决方案。但是,没有提到在 Go 1.2(2012 年末)中引入的 full slice expressions(又名 three-index slices)。使用 one 作为 append 的第一个参数以简洁的方式解决了 OP 的问题。为了完整起见,我在此处包含了该方法:

package main

import "fmt"

func main() {
    args := []string{"2", "3", "8"}
    for i, _ := range args {
        fmt.Println(append(args[:i:i], args[i+1:]...))
    }
    fmt.Println(args)
}

(Playground)

输出:

[3 8]
[2 8]
[2 3]
[2 3 8]