了解切片追加及其对原始切片的影响
在 Go 中使用切片时,append 函数通常用于追加新元素到现有切片。然而,许多开发人员可能会惊讶地发现这个附加操作也可以修改原始切片。
正在检查的代码
考虑以下代码片段:
func someFunc(A []int) int { for i := 0; i < len(A); i++ { 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++ } } } func remove(slice []int, s int) []int { return append(slice[:s], slice[s+1:]...) }
这里,someFunc 函数将切片 A 作为输入,然后创建 A 的一个名为 tempA 的副本,然后调用删除函数从 tempA 中删除元素。检查运行中的函数后,您可能会注意到以下控制台输出:
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,表明在 tempA 上调用append 后原始切片 A 也被修改。乍一看,这种行为似乎违反直觉,因为您希望 A 的按值副本独立于对 tempA 所做的任何更改。
但是,这种现象是切片实现方式的直接结果在围棋中。切片本质上是一种轻量级数据结构,由标头和指向底层数组的指针组成。标头包含有关切片长度和容量的信息,而指针则指向切片中的第一个元素。
将 A 的值分配给 tempA 时,实际上是在创建一个新的切片标头,该标头指向与 A 相同的底层数组。因此,对 tempA 所做的任何更改也将反映在 A 中,因为两个切片都引用相同的数据。
理解切片头和数组
为了进一步掌握这种行为,它有助于理解切片头和数组在 Go 中如何交互。数组包含相同类型的连续元素块。另一方面,切片提供了数组的一部分的动态视图。它描述数组中的一组连续元素,但它不拥有底层数组数据。
当您使用语法 []T{e1, e2, ..., en},您实际上是在创建一个指向数组中第一个元素的新切片头。切片的长度设置为 n,容量设置为切片后数组的剩余长度。
同样,当您使用语法 []T(arr) 创建切片头时,您正在创建一个指向与 arr 相同的底层数组的切片。切片的长度设置为arr的长度,容量设置为arr的容量。
影响和最佳实践
理解切片和数组之间的关系可以帮助您避免潜在的陷阱并编写更高效的 Go 代码。使用切片时,请记住以下几点:
了解 Go 切片的内部结构可以让您利用它们的灵活性和效率,同时确保您的代码按预期运行。通过掌握切片头和数组的细微差别,您可以掌握 Go 中的切片艺术并释放这种多功能数据结构的全部潜力。
以上是为什么追加到 Go 切片的副本也会修改原始切片?的详细内容。更多信息请关注PHP中文网其他相关文章!