我今天在go代码中遇到了奇怪的行为:当我在循环中将elements
追加到slice
然后尝试根据循环的结果创建新的slices
时,最后{{ 1}}覆盖之前append
的{{1}}。
在此特定示例中,这意味着slices
appends
,sliceFromLoop
和j
切片的最后一个元素不是g
,{{1分别是{}和h
,但是......总是100
!
第二个示例 - 101
按预期行事。
102
链接到play.golang: https://play.golang.org/p/INADVS3Ats
经过一些阅读,挖掘和试验后,我发现此问题源于102
引用相同的sliceFromLiteral
个package main
import "fmt"
func create(iterations int) []int {
a := make([]int, 0)
for i := 0; i < iterations; i++ {
a = append(a, i)
}
return a
}
func main() {
sliceFromLoop()
sliceFromLiteral()
}
func sliceFromLoop() {
fmt.Printf("** NOT working as expected: **\n\n")
i := create(11)
fmt.Println("initial slice: ", i)
j := append(i, 100)
g := append(i, 101)
h := append(i, 102)
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h)
}
func sliceFromLiteral() {
fmt.Printf("\n\n** working as expected: **\n")
i := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("initial slice: ", i)
j := append(i, 100)
g := append(i, 101)
h := append(i, 102)
fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h)
}
值,并且可以通过在附加任何内容之前将slices
复制到新的问题来解决但是看起来很......犹豫不决。
基于旧片段创建许多新片段并且不担心更改旧片段值的自觉方式是什么?
答案 0 :(得分:11)
不要将append
分配给除自身以外的任何其他内容。
正如你在问题中提到的,混淆是由于append
都改变了底层数组并返回一个新切片(因为长度可能会改变)。你可以想象它会复制那个支持数组,但它并没有,它只是分配一个指向它的新slice
对象。由于i
永远不会更改,因此所有这些附加内容会将backingArray[12]
的值更改为其他数字。
将此对比与append
数组进行对比,该数组每次都会分配一个新的文字数组。
所以是的,你需要复制切片才能开始工作。
func makeFromSlice(sl []int) []int {
result := make([]int, len(sl))
copy(result, sl)
return result
}
func main() {
i := make([]int, 0)
for ii:=0; ii<11; ii++ {
i = append(i, ii)
}
j := append(makeFromSlice(i), 100) // works fine
}
解释了切片文字行为,因为如果追加量超过支持数组的cap
,则会分配新的数组 。这与切片文字无关,而且与超出限额的内部结构有关。
a := []int{1,2,3,4,5,6,7}
fmt.Printf("len(a) %d, cap(a) %d\n", a, len(a), cap(a))
// len(a) 7, cap(a) 7
b := make([]int, 0)
for i:=1; i<8, i++ {
b = append(b, i)
} // b := []int{1,2,3,4,5,6,7}
// len(b) 7, cap(b) 8
b = append(b, 1) // any number, just so it hits cap
i := append(b, 100)
j := append(b, 101)
k := append(b, 102) // these work as expected now
答案 1 :(得分:3)
如果您需要切片的副本,除了复制切片之外别无他法。您几乎不应该将append
的结果分配给除append
的第一个参数之外的变量。它导致很难找到错误,并且根据切片是否具有所需容量而表现不同。
这不是一个常用的模式,但与所有这些性质的东西一样,如果你需要多次重复几行代码,那么你可以使用一个小辅助函数:
func copyAndAppend(i []int, vals ...int) []int {
j := make([]int, len(i), len(i)+len(vals))
copy(j, i)
return append(j, vals...)
}
答案 2 :(得分:0)
实现copyAndAppend
函数还有一些更简单的方法:
func copyAndAppend(source []string, items ...string) []string {
l := len(source)
return append(source[:l:l], items...)
}
这里我们只确保源没有可用容量,因此强制复制。