为什么在 Go 中追加切片会改变原来的切片?
Why append slice in Go change the original slice?
我有这个代码:
// The input param is A := []int{3, 4, 5, 3, 7}
func someFunc(A []int) int {
...
ways := 0
i := 0
for i < len(A) {
if i+1 == len(A) || i == len(A) {
fmt.Println("break")
break
}
tempA := A // copy the slice by value
fmt.Println("A: ", A)
fmt.Println("tempA: ", A)
fmt.Println()
newArr = remove(tempA, i)
if isAesthetic(newArr) {
ways++
}
i++
}
...
}
func remove(slice []int, s int) []int {
return append(slice[:s], slice[s+1:]...)
}
控制台输出:
A: [3 4 5 3 7]
tempA: [3 4 5 3 7]
A: [4 5 3 7 7]
tempA: [4 5 3 7 7]
A: [4 3 7 7 7]
tempA: [4 3 7 7 7]
A: [4 3 7 7 7]
tempA: [4 3 7 7 7]
变量 A
也发生了变化,而我只是按值将其复制到 tempA
中。而且还是append()
函数,为什么用来追加tempA
的slice也变了?
[]T
类型的切片变量,也称为切片 header,描述了支持数组的连续部分。它与数组数据分开存储。
你可以把它想象成一个包含长度和指向数组中某项(不一定是第一项)的指针的结构。
当你为切片变量赋值时,你实际上是在赋值切片持有的长度和指针header。所以 tempA
最终引用了与 A
相同的后备数组。然后索引将访问相同的底层数组项。
您需要使用copy
函数:
package main
import "fmt"
func main() {
a := []int{3, 4, 5, 3, 7}
// bad
b := a
// good
c := make([]int, len(a))
copy(c, a)
// CHANGES b and a!
b = append(b[:1], b[2:]...)
// c stays the same, a is messed up
// [3 5 3 7 7] [3 5 3 7] [3 4 5 3 7]
fmt.Println(a, b, c)
}
类型 []T
是 slice
,元素类型为 T
。那么什么是slice
呢?
A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).
左边的矩形是切片描述符。描述符有 3 个字段,即指向数组的指针、长度和它指向的数组段的容量。现在,指针指向右侧实际存储元素的后备数组。
假设,你有一个切片:
x := make([]byte, 5, 5)
然后它会指向一个后备数组 [5]byte
正如您在图中看到的那样。
现在,如果你这样做:
y := x
以为会被复制,但事实并非如此。它只是创建一个新的切片描述符,指向 x
指向的同一个后备数组。
因此,有一个名为 copy 的内置程序可以帮助您复制(正是您想要的)
The copy built-in function copies elements from a source slice into a destination slice.
使用 copy
,您还可以获得目标站点指向的新支持数组。
要了解有关切片的更多信息,请阅读 this。
我有这个代码:
// The input param is A := []int{3, 4, 5, 3, 7}
func someFunc(A []int) int {
...
ways := 0
i := 0
for i < len(A) {
if i+1 == len(A) || i == len(A) {
fmt.Println("break")
break
}
tempA := A // copy the slice by value
fmt.Println("A: ", A)
fmt.Println("tempA: ", A)
fmt.Println()
newArr = remove(tempA, i)
if isAesthetic(newArr) {
ways++
}
i++
}
...
}
func remove(slice []int, s int) []int {
return append(slice[:s], slice[s+1:]...)
}
控制台输出:
A: [3 4 5 3 7]
tempA: [3 4 5 3 7]
A: [4 5 3 7 7]
tempA: [4 5 3 7 7]
A: [4 3 7 7 7]
tempA: [4 3 7 7 7]
A: [4 3 7 7 7]
tempA: [4 3 7 7 7]
变量 A
也发生了变化,而我只是按值将其复制到 tempA
中。而且还是append()
函数,为什么用来追加tempA
的slice也变了?
[]T
类型的切片变量,也称为切片 header,描述了支持数组的连续部分。它与数组数据分开存储。
你可以把它想象成一个包含长度和指向数组中某项(不一定是第一项)的指针的结构。
当你为切片变量赋值时,你实际上是在赋值切片持有的长度和指针header。所以 tempA
最终引用了与 A
相同的后备数组。然后索引将访问相同的底层数组项。
您需要使用copy
函数:
package main
import "fmt"
func main() {
a := []int{3, 4, 5, 3, 7}
// bad
b := a
// good
c := make([]int, len(a))
copy(c, a)
// CHANGES b and a!
b = append(b[:1], b[2:]...)
// c stays the same, a is messed up
// [3 5 3 7 7] [3 5 3 7] [3 4 5 3 7]
fmt.Println(a, b, c)
}
类型 []T
是 slice
,元素类型为 T
。那么什么是slice
呢?
A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).
左边的矩形是切片描述符。描述符有 3 个字段,即指向数组的指针、长度和它指向的数组段的容量。现在,指针指向右侧实际存储元素的后备数组。
假设,你有一个切片:
x := make([]byte, 5, 5)
然后它会指向一个后备数组 [5]byte
正如您在图中看到的那样。
现在,如果你这样做:
y := x
以为会被复制,但事实并非如此。它只是创建一个新的切片描述符,指向 x
指向的同一个后备数组。
因此,有一个名为 copy 的内置程序可以帮助您复制(正是您想要的)
The copy built-in function copies elements from a source slice into a destination slice.
使用 copy
,您还可以获得目标站点指向的新支持数组。
要了解有关切片的更多信息,请阅读 this。