Golang中Slice处理疑似存在错误问题探讨
Golang中Slice处理疑似存在错误问题探讨
请解释一下这段代码的奇怪行为(输出仅取决于 main() 中的 n 值)代码在此
我使用的 Go 版本是:go1.14.4 windows/amd64
package main
import "fmt"
func fn(s []st) {
var y st
y = st{"fn()", 777}
s = append(s, y)
fmt.Println("1. fn appends:", s)
s[0].a = "???"
s[len(s)-1].a = "!!!"
fmt.Println("2. fn changes:", s)
s = s[:0]
fmt.Println("3. fn deletes all:", s)
}
type st struct {
a string
b int
}
func main() {
n := 2 // 将 n 改为 1, 2, 3, 4, ...
var x []st
for i := 0; i < n; i++ {
x = append(x, st{"main", i})
}
fmt.Println("main before fn(): ", x)
fn(x)
fmt.Println("main after fn(): ", x)
}
输出:
main before fn(): [{main 0} {main 1}]
1. fn appends: [{main 0} {main 1} {fn() 777}]
2. fn changes: [{??? 0} {main 1} {!!! 777}]
3. fn deletes all: []
main after fn(): [{main 0} {main 1}]
n = 2
当 n :=2 时,输出的最后一行 x[0].a == "main" (请看上面)
当 n :=3 时,输出的最后一行 x[0].a == "???" (请看下面)
输出:
main before fn(): [{main 0} {main 1} {main 2}]
1. fn appends: [{main 0} {main 1} {main 2} {fn() 777}]
2. fn changes: [{??? 0} {main 1} {main 2} {!!! 777}]
3. fn deletes all: []
main after fn(): [{??? 0} {main 1} {main 2}]
n = 3
更多关于Golang中Slice处理疑似存在错误问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你好 Conner
详尽的回答。 非常感谢!
更多关于Golang中Slice处理疑似存在错误问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
没问题!
希望这些内容能有所帮助。 我对 Go 语言还比较新,所以我的解释可能不够清晰 😂
你好,Vladimir!
我认为有几件事导致了困惑。
当你在这里向切片追加元素时
var y st
y = st{"fn()", 777}
s = append(s, y)
fmt.Println("1. fn appends:", s)
我认为你分配了一个新的底层数组,因为容量已经超出了。这就是为什么你在最终的 println 语句中没有看到 “???” 和 “!!!” 反映出来的原因。
如果你像我在这里所做的那样注释掉那些行,那么就不会创建新的底层数组,因此你可以在 main 函数的最终 println 语句中看到这些更改的反映。
查看这个 Playground 以了解新底层数组是如何创建的,以及 s 的容量变为 4,而 main 中 x 的容量仍为 2:https://play.golang.org/p/oCBg1lzrCEV
希望这有所帮助。
我知道我并没有试图回答为什么 s =[:0] 不影响 main 中的切片,但我认为这与实际上没有影响底层数组有关,而只是改变了 s 指向它的哪一部分,而 x 仍然指向完整的底层数组。
这是一个典型的 slice 底层数组共享和扩容问题。关键在于 append 操作是否会触发底层数组重新分配。
当 n=2 时:
func fn(s []st) {
// s 的 cap=2,len=2
s = append(s, y) // 需要扩容,创建新数组
// 此时 s 指向新数组,与原数组分离
s[0].a = "???" // 修改的是新数组,不影响原数组
}
当 n=3 时:
func fn(s []st) {
// s 的 cap=4(Go的扩容策略),len=3
s = append(s, y) // 不需要扩容,使用原数组
// 此时 s 仍指向原数组
s[0].a = "???" // 修改的是原数组,影响 main 中的 x
}
示例代码验证:
package main
import "fmt"
func showSliceInfo(name string, s []int) {
fmt.Printf("%s: len=%d cap=%d %v\n", name, len(s), cap(s), s)
}
func main() {
// 情况1:容量足够,共享数组
s1 := make([]int, 2, 4)
s1[0], s1[1] = 1, 2
showSliceInfo("s1", s1)
s2 := append(s1, 3)
showSliceInfo("s2", s2)
s2[0] = 999
fmt.Println("s1[0] after s2[0]=999:", s1[0]) // 输出 999
// 情况2:容量不足,新建数组
s3 := make([]int, 2, 2)
s3[0], s3[1] = 1, 2
showSliceInfo("s3", s3)
s4 := append(s3, 3)
showSliceInfo("s4", s4)
s4[0] = 999
fmt.Println("s3[0] after s4[0]=999:", s3[0]) // 输出 1
}
在你的代码中,当 n=2 时初始容量为2,append 需要扩容;当 n=3 时初始容量为4(Go的扩容策略),append 不需要扩容,导致底层数组共享。

