在使用切片的append函数时遇到的问题

huangapple go评论76阅读模式
英文:

Golang: Problems when using append on slice

问题

我正在使用golang。这是我的代码:

func main() {
    quanPailie([]int{1, 2})
}

func quanPailie(nums []int) [][]int {
    COUNT := len(nums)

    //只有一个元素
    if COUNT == 1 {
        return [][]int{nums}
    }

    insertItem(quanPailie(nums[:COUNT-1]), nums[COUNT-1])
    return [][]int{}
}

func insertItem(res [][]int, insertNum int) {
    fmt.Println("insertItem,res:", res, "insertNum", insertNum) //insertItem,res: [[1]] insertNum 2

    for _, v := range res {
        for i := 0; i < len(v); i++ {
            fmt.Println("===before,v:", v)
            c := append(v[:i], append([]int{insertNum}, v[i:]...)...)
            fmt.Println("===after,v:", v)

            fmt.Println("ccc", c)

        }
    }
}

让我非常困惑的是输出结果:

===before,v: [1]
===after,v: [2]

为什么v的值会改变?希望有人能帮助我。非常感谢。

Go playground: https://play.golang.org/p/wITYsGpX7U

编辑:
感谢icza的帮助,我想我已经理解了这个问题。
这里有一个简单的代码来展示这个问题。

func test1() {
    nums := []int{1, 2, 3}
    _ = append(nums[:2], 4)
    fmt.Println("test1:", nums)

    //nums改变了,因为容量足够大,原始数组被修改了。
}

func test2() {
    nums := []int{1, 2, 3}
    c := append(nums[:2], []int{4, 5, 6}...)
    fmt.Println("test2:", nums)
    fmt.Println("cc:", c)

    //nums没有改变,因为容量不够大。
    //一个新的数组被分配,而nums仍然指向旧的数组。
    //当然,append的返回值指向新的数组。
}

Go playground: https://play.golang.org/p/jBNFsCqUn3

英文:

I am using golang. Here is my code:

func main() {
	quanPailie([]int{1, 2})
}

func quanPailie(nums []int) [][]int {
	COUNT := len(nums)

	//only one item
	if COUNT == 1 {
		return [][]int{nums}
	}

	insertItem(quanPailie(nums[:COUNT-1]), nums[COUNT-1])
	return [][]int{}
}

func insertItem(res [][]int, insertNum int) {
	fmt.Println(&quot;insertItem,res:&quot;, res, &quot;insertNum&quot;, insertNum) //insertItem,res: [[1]] insertNum 2

	for _, v := range res {
		for i := 0; i &lt; len(v); i++ {
			fmt.Println(&quot;===before,v:&quot;, v)
			c := append(v[:i], append([]int{insertNum}, v[i:]...)...)
			fmt.Println(&quot;===after,v:&quot;, v)

			fmt.Println(&quot;ccc&quot;, c)

		}
	}
}

What makes me very confused is the output:

===before,v: [1]
===after,v: [2]

Why did the value of v change? Hope someone can help me. Thanks a lot.

Go playground: https://play.golang.org/p/wITYsGpX7U

EDIT:<br/>
Thanks for icza's great help, I think I have understood this problem.<br>
And, here is a simple code to show this issue.

func test1() {
	nums := []int{1, 2, 3}
	_ = append(nums[:2], 4)
	fmt.Println(&quot;test1:&quot;, nums)

	//nums changes because the cap is big enought, the original array is modified.

}

func test2() {
	nums := []int{1, 2, 3}
	c := append(nums[:2], []int{4, 5, 6}...)
	fmt.Println(&quot;test2:&quot;, nums)
	fmt.Println(&quot;cc:&quot;, c)

	//nums dont&#39;t change because the cap isn&#39;t big enought.
	//a new array is allocated while the nums still points to the old array.
	//Of course, the return value of append points to the new array.
}

Go playground: https://play.golang.org/p/jBNFsCqUn3

答案1

得分: 4

这是问题中的代码:

fmt.Println("===before,v:", v)
c := append(v[:i], append([]int{insertNum}, v[i:]...)...)
fmt.Println("===after,v:", v)

你问为什么在两个Println()语句之间v发生了变化。

因为你使用了内置的append()函数,引用自其文档:

> append内置函数将元素追加到切片的末尾。**如果切片有足够的容量,目标切片将被重新切片以容纳新元素。**如果没有足够的容量,将分配一个新的底层数组。append函数返回更新后的切片。

所以,如果你要追加的切片有足够的空间(容量)来容纳元素,不会分配新的切片,而是会重新切片目标切片(使用相同的底层数组),并在其中进行追加操作。

让我们来检查一下容量:

fmt.Println("===before,v:", v, cap(v))
c := append(v[:i], append([]int{insertNum}, v[i:]...)...)
fmt.Println("===after,v:", v, cap(v))

输出结果:

===before,v: [1] 2
===after,v: [2] 2

切片v的容量为2。当for循环开始时,i=0v[:i]v[:0],它是一个空切片(但容量为2),因此追加1个或2个元素都不会分配新的数组/切片,而是在原地进行。这个原地操作是在v的第0个元素上进行的,因为v[:i]v[0:i]的简写。因此,元素将从共享的底层数组的v[0]开始追加,所以被v[0]表示的元素会发生变化。

请注意,对切片进行切片操作会得到一个与原始切片共享底层数组的切片(不会复制元素)。

如果你想避免这种情况,可以使用或分配一个新的切片,复制原始内容并追加到新的切片中,例如:

src := []int{1, 2}
c := make([]int, len(src))
copy(c, src)
// 追加一些内容:
c = append(c, 3, 4)

fmt.Println(src) // [1 2] - src不会改变
fmt.Println(c)   // [1 2 3 4]
英文:

This is the code in question:

fmt.Println(&quot;===before,v:&quot;, v)
c := append(v[:i], append([]int{insertNum}, v[i:]...)...)
fmt.Println(&quot;===after,v:&quot;, v)

You ask why v changes between the 2 Println() statements.

Because you are using the builtin append() function, quoting from its doc:

> The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice.

So if the slice you append to has enough room (capacity) to accomodate the elements you want to append, no new slice will be allocated, instead the destination slice will be re-sliced (which will use the same underlying array) and append will happen in that.

Let's check the capacity:

fmt.Println(&quot;===before,v:&quot;, v, cap(v))
c := append(v[:i], append([]int{insertNum}, v[i:]...)...)
fmt.Println(&quot;===after,v:&quot;, v, cap(v))

Output:

===before,v: [1] 2
===after,v: [2] 2

The v slice has a capacity of 2. When for loop starts, i=0, v[:i] is v[:0] which is an empty slice (but has capacity 2) and so appending 1 or 2 elements will not allocate a new array/slice, it will be done "in place". This "in place" is the 0th element of v, since v[:i] is shorthand for v[0:i]. Hence the elements will be appended starting from v[0] in the underlying array which is shared, so the element denoted by v[0] will change.

Note that slicing a slice results in a slice which shares its underlying backing array with the original slice (does not make a copy of the elements).

If you want to avoid this, use or allocate a new slice, copy original content and append to the new slice, e.g.:

src := []int{1, 2}
c := make([]int, len(src))
copy(c, src)
// Append something:
c = append(c, 3, 4)

fmt.Println(src) // [1 2] - src doesn&#39;t change
fmt.Println(c)   // [1 2 3 4]

huangapple
  • 本文由 发表于 2015年7月13日 17:59:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/31380366.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定