我怎么能遍历切片并传递除当前元素之外的切片?似乎 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)
但切片的容量对我来说是个秘密,我不明白为什么我会超过它。
答案 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
- 也就是0
,1
和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"}
tl; dr 您应始终保存append
的结果,如果您想制作副本以便进行更改,请自行制作副本。
答案 2 :(得分:1)
您可以将切片想象为由固定大小的数组组成的结构,以及其中元素数量的计数器。切片的容量是底层数组的大小,而切片的长度是计数器。
追加定义方式为:func append(slice []Type, elems ...Type) []Type
(godoc),这实际上意味着您将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)
}
输出:
[3 8]
[2 8]
[2 3]
[2 3 8]