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

4 回复

你好 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,而 mainx 的容量仍为 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 不需要扩容,导致底层数组共享。

回到顶部