Golang中多维切片追加元素的意外行为解析
Golang中多维切片追加元素的意外行为解析 我发现向切片追加元素时出现了一个奇怪的行为:
为什么
package main
import (
"fmt"
)
var (
a []int
aa [][]int
)
func main() {
a = make([]int, 4, 4)
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
for i := 0; i < 4; i++ {
a[0] = a[0] * 2
a[1] = a[1] * 3
a[2] = a[2] * 4
a[3] = a[3] * 5
aa = append(aa, []int{a[0], a[1], a[2], a[3]}) //[[2 6 12 20] [4 18 48 100] [8 54 192 500] [16 162 768 2500]]
//aa = append(aa, a) //[[16 162 768 2500] [16 162 768 2500] [16 162 768 2500] [16 162 768 2500]]
//aa = append(aa, a[:]) //[[16 162 768 2500] [16 162 768 2500] [16 162 768 2500] [16 162 768 2500]]
}
fmt.Println(aa)
}
会得到结果
[[2 6 12 20] [4 18 48 100] [8 54 192 500] [16 162 768 2500]]
然而,追加整个切片
…
aa = append(aa, a)
…
却得到结果
[[16 162 768 2500] [16 162 768 2500] [16 162 768 2500] [16 162 768 2500]]
?
甚至通过复制切片
…
aa = append(aa, a[:])
…
也会导致这种恼人的行为。
如果这不是一个错误,那至少也是一个非常糟糕的特性!

更多关于Golang中多维切片追加元素的意外行为解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html
如果不使用 make 创建一个新的切片,那么你将反复共享同一个切片。
func main() {
fmt.Println("hello world")
}
更多关于Golang中多维切片追加元素的意外行为解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
尝试类似 Go Playground - Go 编程语言 的内容。
感谢您的详细解释!
在这种情况下,这是一个不那么快捷且更“粗糙”的解决方案:
…
var b []int
b = make([]int, 4, 4)
for i := 0; i < len(a); i++ {
b[i] = a[i]
}
aa = append(aa, b)
…
但这并不直观,并且绝对是难以发现的错误来源。
是的,最终结果是 aa 中的每个元素都指向同一个 a 的实例。
因此,如果你将 a[0] 的值加倍,那么 aa[0][0]、aa[1][0]、aa[2][0] 和 aa[3][0] 的值也都会加倍,因为它们都指向同一个内存地址。这就是切片的工作原理。
请参阅 Go Slices: usage and internals - The Go Programming Language
我曾尝试过
copy(b,a)
同样,但我犯了一个错误,只分配了一次内存,并在循环内重新赋值。这没有奏效:
for … {
…
a[0] = a[0] * 2
…
b := make([]int, 4, 4)
copy(b, a)
aa = append(aa, b)
…
}
你的方法成功了,但变量 b 必须每次都重新分配。
b := make([]int, 4, 4)
for … {
…
a[0] = a[0] * 2
…
copy(b, a)
aa = append(aa, b)
…
}
这个行为不是错误,而是切片底层数组共享导致的预期行为。当您使用 append(aa, a) 或 append(aa, a[:]) 时,实际上是在向 aa 追加指向同一个底层数组的切片引用。
以下是关键点解析:
- 切片是引用类型:切片本身不存储数据,而是指向一个底层数组
a和a[:]指向相同的底层数组:两者都引用相同的存储空间- 循环中修改
a会影响所有已追加的切片:因为所有追加的切片都指向同一个底层数组
示例代码演示了这个问题:
package main
import "fmt"
func main() {
var aa [][]int
a := make([]int, 4, 4)
for i := 0; i < 4; i++ {
// 填充初始值
for j := 0; j < 4; j++ {
a[j] = (i+1)*(j+1)
}
// 错误方式:追加同一个切片的引用
aa = append(aa, a) // 所有元素都指向同一个底层数组
// 正确方式:创建新切片
// aa = append(aa, []int{a[0], a[1], a[2], a[3]})
}
// 修改原始切片
a[0] = 999
// 打印结果 - 所有元素都显示修改后的值
fmt.Println(aa) // [[999 2 3 4] [999 2 3 4] [999 2 3 4] [999 2 3 4]]
}
要解决这个问题,需要在每次迭代时创建新的切片副本:
package main
import "fmt"
func main() {
var aa [][]int
a := make([]int, 4, 4)
for i := 0; i < 4; i++ {
// 填充值
for j := 0; j < 4; j++ {
a[j] = (i+1)*(j+1)
}
// 方法1:显式创建新切片
newSlice := make([]int, len(a))
copy(newSlice, a)
aa = append(aa, newSlice)
// 方法2:使用切片字面量(编译器会创建新数组)
// aa = append(aa, []int{a[0], a[1], a[2], a[3]})
// 方法3:使用 append 复制
// aa = append(aa, append([]int{}, a...))
}
// 修改原始切片不会影响 aa 中的元素
a[0] = 999
fmt.Println(aa) // 每个元素保持各自的值
}
这种行为设计是为了提高性能,避免不必要的内存分配和复制。理解切片底层数组的共享机制对于正确使用Go语言至关重要。

