slice 的底层实现是一个包含三个字段的结构体:指向底层数组的指针、slice 的长度和 slice 的容量。当我们对 slice 进行操作时,例如添加或删除元素,实际上是在底层数组中进行操作。由于 slice 是一个指向底层数组的指针,因此多个 slice 可以共享同一个底层数组,这也是 slice 被称为引用类型的原因,也因此可以将其赋值为 nil(实际上是将底层结构体中的指向底层数组的指针设置为 nil)。
需要注意的是,虽然 slice 是引用类型,但是它并不是一个指针类型。因此,我们可以对 slice 进行赋值和传递,而不需要使用指针。这也是 Go 语言中 slice 的一个优点,它可以方便地进行传递和复制,而不需要担心底层数组的复制和传递带来的性能问题。
除了 slice,Go 语言中还有其他的引用类型,例如 map 和 channel
这里要分具体情况,如果在函数中对 slice 进行了 append 操作导致了 slice 扩容,那么扩容之后的 slice 的底层数据与原 slice 分离,就不会相互影响,否则会影响原 slice
// 初始化一个长度和容量不相同的 slice,函数中 append 操作没有导致底层数据扩容 // 所以对 slice[0] 的修改会影响原 slice func Test_isSliceChangeIfAppend(t *testing.T) { slice := make([]int, 5, 10) fmt.Printf("origin slice:%v\n", slice) isSliceChangeIfAppend(slice) fmt.Printf("after append out func:%v\n", slice) } func isSliceChangeIfAppend(origin []int) { origin = append(origin, 1, 2, 3) origin[0] = 1 fmt.Printf("after append in func:%v\n", origin) } -----output----- origin slice:[0 0 0 0 0] after append in func:[1 0 0 0 0 1 2 3] after append out func:[1 0 0 0 0]
// 同上面的代码,初始化一个长度和容量相同的 slice // 函数中的 append 操作就一定会扩容,后续修改也就不会影响原 slice func Test_isSliceChangeIfAppend(t *testing.T) { // 初始化一个长度和容量相同的 slice slice := make([]int, 5) fmt.Printf("origin slice:%v\n", slice) isSliceChangeIfAppend(slice) fmt.Printf("after append out func:%v\n", slice) } func isSliceChangeIfAppend(origin []int) { origin = append(origin, 1, 2, 3) origin[0] = 1 fmt.Printf("after append in func:%v\n", origin) } -----output----- origin slice:[0 0 0 0 0] after append in func:[1 0 0 0 0 1 2 3] after append out func:[0 0 0 0 0]
![图片](https://mmbiz.qpic.cn/mmbiz_png/2fBauyxIV9YmLFQeheVRlib675c1S34fYGl7Y1RGmdwISIncPcibic1phDVRPaqUZKJUCx0lINWdqtWcJOpArFsKA/640?wx_fmt=png)