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
函数中,最开始s1
和s2
地址为什么不同?
在 sliceNature
函数中,s1
和 s2
的地址不同的原因是因为它们是两个独立的变量,尽管它们引用了同一个底层数组。
当我们声明并初始化一个切片时,Go 语言会为该切片分配内存,并将底层数组的引用赋值给切片变量。在这种情况下,s1
和 s2
是两个不同的变量,它们的地址是不同的。
当我们执行 s2 := s1
时,实际上是将 s1
的值(底层数组的引用)赋值给了 s2
。这意味着 s1
和 s2
引用了同一个底层数组。因此,当我们修改 s2
的元素时,底层数组的对应元素也会被修改,从而影响到 s1
。
需要注意的是,尽管 s1
和 s2
引用了同一个底层数组,它们仍然是两个独立的变量。所以它们具有不同的地址,但是它们的值(即底层数组的引用)是相同的。
即: 切片变量具有自己的地址,但是它们引用的底层数组可能是相同的。在
sliceNature
函数中,s1
和s2
引用了同一个底层数组。因此,它们的地址是不同的。
那么,新的问题又产生了
不是说共用同一个底层数组吗?为什么s1和s2是新的变量呢?底层数组也有地址吗?
当然,在 Go 语言中,切片是对底层数组的引用,而底层数组是在内存中分配的一块连续空间。底层数组有自己的地址,但在切片的上下文中,我们不能直接访问到底层数组的地址。
当我们创建一个切片时,Go 语言会自动为切片和底层数组分配内存,并建立引用关系。我们可以通过切片变量来访问和操作底层数组的元素,但无法直接获取底层数组的地址。
切片本身是一个结构体,包含了指向底层数组的指针、切片的长度和容量等信息。通过切片变量,我们可以间接访问到底层数组的内容,并对其进行修改。
如果我们想获取底层数组的地址,可以使用 &
运算符获取切片中第一个元素的地址,然后通过类型转换将其转换为指向底层数组的指针。例如,&s[0]
可以获取切片 s
底层数组的地址。
需要注意的是,底层数组的地址可能会在切片扩容时发生改变。当切片的容量不足以容纳新的元素时,Go 语言会自动分配一个更大的底层数组,并将旧的元素复制到新的底层数组中。这时,切片将引用新的底层数组,而旧的底层数组可能会被垃圾回收。因此,在切片扩容后,底层数组的地址可能会发生变化。
综上所述,底层数组有自己的地址,但在切片的上下文中,我们无法直接访问到底层数组的地址。我们可以通过切片变量间接访问和操作底层数组的内容。如果需要获取底层数组的地址,可以使用
&
运算符获取切片中第一个元素的地址,并进行类型转换。
关于切片作为地址传参,可以看看 slice作为函数参数时何时需要传地址? 这篇文章