切片内容修改的些许理解

2023-12-05 18:42:18 Go Go小卡片

在 Go 语言中,切片是对底层数组的引用,而底层数组是在内存中分配的一块连续空间。不同切片可以拥有同一个底层数组。当你将一个切片赋值给另一个切片时,它们引用的是同一个底层数组。因此,修改其中一个切片的元素会影响到另一个切片。

代码案例:

func sliceNature() {
    s1 := []int{0, 1, 2, 3, 4, 5}
    // 将s1的元素索引为2的元素(2)替换为100
    s2 := s1
    s2[0] = 100
    fmt.Printf("s1: %v, s1地址:%p\n", s1, &s1) // s1: [100 1 2 3 4 5], s1地址:0xc000008048
    fmt.Printf("s2: %v, s2地址:%p\n", s2, &s2) // s2: [100 1 2 3 4 5], s2地址:0xc000008060
}

由于修改拥有同一底层数组的切片,产生的影响就是一旦其中某个切片对底层数组内容进行修改,那么引用这个底层数组的所有切片内容都已发生改变

所以想要保证切片修改时不影响到其他切片,我们需要通过特定的方法去实现

组合技:

  • 先通过make()获取一片新的内存空间(也就是底层数组)
  • 然后通过copy()函数将原来的切片内容赋值给新的切片
  • 最后就可以通过修改新的切片,从而不影响原切片内容了

如下面这个代码案例所示:

func copyUpdate() {
    s1 := []int{1, 2, 3, 4, 5}

    // 固定写法,先利用make重新获取一片新的地址空间
    s2 := make([]int, len(s1))
    // 等同于 s2 := []int{0, 0, 0, 0, 0}

    // 再copy一下s1的内容到s2
    copy(s2, s1)
    // 修改s2的内容
    s2[0] = 100

    // 打印输出
    fmt.Printf("s1: %v, s1地址:%p\n", s1, &s1) // s1: [1 2 3 4 5], s1地址:0xc000008048
    fmt.Printf("s2: %v, s2地址:%p\n", s2, &s2) // s2: [100 2 3 4 5], s2地址:0xc000008060
}

copyUpdate 函数中,我们使用了 make 函数创建了一个新的切片 s2,并使用 copy 函数将 s1 的内容复制到 s2 中。这样,s2 引用了一个新的底层数组,而不是和 s1 共享同一个底层数组。因此,当你修改 s2[0] 的值为 100 时,只有 s2 受到了影响,而 s1 保持不变。

总结起来,通过赋值操作或使用 copy 函数创建切片时,它们引用的是同一个底层数组。但是,使用 make 函数创建新的切片会分配一个新的底层数组,并复制原始切片的内容到新的底层数组中。

那么,问题来了,为什么在sliceNature函数中,最开始s1s2地址为什么不同?

sliceNature 函数中,s1s2 的地址不同的原因是因为它们是两个独立的变量,尽管它们引用了同一个底层数组。

当我们声明并初始化一个切片时,Go 语言会为该切片分配内存,并将底层数组的引用赋值给切片变量。在这种情况下,s1s2 是两个不同的变量,它们的地址是不同的

当我们执行 s2 := s1 时,实际上是将 s1 的值(底层数组的引用)赋值给了 s2。这意味着 s1s2 引用了同一个底层数组。因此,当我们修改 s2 的元素时,底层数组的对应元素也会被修改,从而影响到 s1

需要注意的是,尽管 s1s2 引用了同一个底层数组,它们仍然是两个独立的变量。所以它们具有不同的地址,但是它们的值(即底层数组的引用)是相同的。

即: 切片变量具有自己的地址,但是它们引用的底层数组可能是相同的。在 sliceNature 函数中,s1s2 引用了同一个底层数组。因此,它们的地址是不同的。

那么,新的问题又产生了

不是说共用同一个底层数组吗?为什么s1和s2是新的变量呢?底层数组也有地址吗?

当然,在 Go 语言中,切片是对底层数组的引用,而底层数组是在内存中分配的一块连续空间。底层数组有自己的地址,但在切片的上下文中,我们不能直接访问到底层数组的地址。

当我们创建一个切片时,Go 语言会自动为切片和底层数组分配内存,并建立引用关系。我们可以通过切片变量来访问和操作底层数组的元素,但无法直接获取底层数组的地址。

切片本身是一个结构体,包含了指向底层数组的指针、切片的长度和容量等信息。通过切片变量,我们可以间接访问到底层数组的内容,并对其进行修改。

如果我们想获取底层数组的地址,可以使用 & 运算符获取切片中第一个元素的地址,然后通过类型转换将其转换为指向底层数组的指针。例如,&s[0] 可以获取切片 s 底层数组的地址。

需要注意的是,底层数组的地址可能会在切片扩容时发生改变。当切片的容量不足以容纳新的元素时,Go 语言会自动分配一个更大的底层数组,并将旧的元素复制到新的底层数组中。这时,切片将引用新的底层数组,而旧的底层数组可能会被垃圾回收。因此,在切片扩容后,底层数组的地址可能会发生变化。

综上所述,底层数组有自己的地址,但在切片的上下文中,我们无法直接访问到底层数组的地址。我们可以通过切片变量间接访问和操作底层数组的内容。如果需要获取底层数组的地址,可以使用 & 运算符获取切片中第一个元素的地址,并进行类型转换。

关于切片作为地址传参,可以看看 slice作为函数参数时何时需要传地址? 这篇文章