有个难题请教一下大家,slice 切片扩容导致的问题

网上看到了一篇文章,里面有一个很有意思的问题,但是解释的不是很明白,我在网上也没找到解答,所以在这里想请教一下大家,感谢!

package main
import "fmt"

func main() {
    s3 := make([]int, 2, 10)
    fmt.Println(s3) // [0 0]
    Test2(s3)
    fmt.Println(s3) // [0 0]

    s4 := s3[0:10]
    s4[0] = 100
    fmt.Println(s3) // [100 0]
    fmt.Println(s4) // [100 0 6 6 6 0 0 0 0 0]
}
func Test2(s []int) {
    s = append(s, 6)
    s = append(s, 6)
    s = append(s, 6)
    fmt.Println(s) // [0 0 6 6 6]
}

得出的结果是:
[0 0]
[0 0 6 6 6]
[0 0]
[100 0]
[100 0 6 6 6 0 0 0 0 0]

不太明白,第三个结果为什么是 [0 0],而不是 [0 0 6 6 6] 呢
在 Test2 函数中,使用 append 的时候,并没有扩容,添加的数据还在底层数组的容量中,那为啥在 Test2 函数之后 s3 是 [0 0], s4:=s3 [0:10] 是 [100 0 6 6 6 0 0 0 0 0],为什么不是 [100, 0] 呢,修改了 s4 [0] 的值,s3 的第一个值也变成了 100.

想了很久都没找到答案

麦迪文
最佳答案

不太明白,第三个结果为什么是 [0 0],而不是 [0 0 6 6 6] 呢

因为你这个是值传递参数的方式,只是将 s3 当作值传递给了 Test2。如果你使用引用传递参数的方式,也就是将 Test2 更改为 func Test2(s *[]int) 这种,那么你就可以得到你上面疑问的答案。

s4:=s3 [0:10] 是 [100 0 6 6 6 0 0 0 0 0],为什么不是 [100, 0] 呢,修改了 s4 [0] 的值,s3 的第一个值也变成了 100.

这个是因为切片赋值操作是浅拷贝的方式,s3 和 s4 其实还是共享同一个内存地址,那么修改 s4 的内部会导致 s3 的内部一并被改动。 fmt.Printf("s3 addr: %p, s4 addr: %p\n", s3, s4) 你可以看到他们的地址其实是相同的。

可以看看这篇文章: yuminlee2.medium.com/golang-deep-a...

1年前 评论
麦迪文 (楼主) 1年前
讨论数量: 4

1、对于 “不太明白,第三个结果为什么是 [0 0],而不是 [0 0 6 6 6] 呢!” 我的解释:slice 本身是一个结构体:

type slice struct {
  array unsafe.Pointer  //指向底层数组的指针
  len   int  //长度
  cap   int  //容量
}

我们知道 go 是值传递,也就是 copy,所以 Test2 中的 s 和 s3 此时不是同一个切片,不发生扩容的情况,s 的 len 和 cap 不会影响到 s3, 但是他们都指向同一个底层数组,因为 s 进行 append 了,所以底层数据也就发生变化了, 如果你这样写:

func Test2(s *[]int) {
    *s = append(*s, 6)
    *s = append(*s, 6)
    *s = append(*s, 6)
    fmt.Println(*s) // [0 0 6 6 6]
}

就会达到你的预期,因为传入的是指针,就是整个 s3 的地址,len 和 cap 值也会随之变化。

2、对于这操作:s4 := s3[0:10] 我的理解是他是对底层数组的一个取值,如果我们这里弄一个 item := s3[3] 一定会报: panic: runtime error: index out of range [3] with length 2, 我们可以这样验证:

    fmt.Println(s3[0:4])  // [0 0 6 6]
    s4 := s3[0:5]   // s4: [0 0 6 6 6]
    fmt.Println("s4:", s4)  // s4: [0 0 6 6 6]
    fmt.Println(s4[0:10])  // [0 0 6 6 6 0 0 0 0 0]

3、对应 “修改了 s4 [0] 的值,s3 的第一个值也变成了 100” 因为他们共享底层数组 这是我的一点理解, 不知道对不对

1年前 评论
麦迪文 (楼主) 1年前

不太明白,第三个结果为什么是 [0 0],而不是 [0 0 6 6 6] 呢

因为你这个是值传递参数的方式,只是将 s3 当作值传递给了 Test2。如果你使用引用传递参数的方式,也就是将 Test2 更改为 func Test2(s *[]int) 这种,那么你就可以得到你上面疑问的答案。

s4:=s3 [0:10] 是 [100 0 6 6 6 0 0 0 0 0],为什么不是 [100, 0] 呢,修改了 s4 [0] 的值,s3 的第一个值也变成了 100.

这个是因为切片赋值操作是浅拷贝的方式,s3 和 s4 其实还是共享同一个内存地址,那么修改 s4 的内部会导致 s3 的内部一并被改动。 fmt.Printf("s3 addr: %p, s4 addr: %p\n", s3, s4) 你可以看到他们的地址其实是相同的。

可以看看这篇文章: yuminlee2.medium.com/golang-deep-a...

1年前 评论
麦迪文 (楼主) 1年前