图解Go中Slice原理

175 阅读3分钟

从一道例子开始

package main

import (
	"fmt"
)

func main() {
	// 初始化一个 slice
	slice := []int{1, 2, 3}
	fmt.Println("原始 slice:", slice, len(slice), cap(slice))

	// 直接传递 slice
	modifySlice(slice)
	fmt.Println("直接传递 slice 后:", slice, len(slice), cap(slice)) // 直接传递 slice 后的修改会影响底层数组,但不影响 len 和 cap

	// 传递 slice 的指针
	modifySlicePointer(&slice)
	fmt.Println("传递 slice 指针后:", slice, len(slice), cap(slice)) // 传递 slice 指针后,函数修改了 slice 的长度和容量
}

// 直接传递 slice 的副本
func modifySlice(s []int) {
	s[0] = 10        // 修改底层数组的值会影响原始 slice
	s = append(s, 4) // 这里的修改不会影响原始 slice 的长度和容量。后续的输出slice不会包含4
}

// 传递 slice 的指针
func modifySlicePointer(s *[]int) {
	(*s)[0] = 20       // 修改底层数组的值会影响原始 slice
	*s = append(*s, 5) // 通过指针修改了 slice 的长度和容量
}

代码解释

直接传递切片:
  • modifySlice(slice) 直接将切片 slice 传递给函数。因为切片本质上是一个结构体,传递切片实际上是传递了一个副本,包括指向底层数组的指针、长度和容量。
  • modifySlice 函数中,首先将切片的第一个元素修改为 10,这会影响到原始切片,因为切片的底层数组是共享的。
  • 然后调用 append(s, 4),将 4 添加到切片中。由于这个操作导致底层数组容量不足,Go 会分配新的底层数组,生成的新切片指向这个新的数组,因此这个修改不会影响到原始切片的长度和容量。
  • main 中打印出 slice 的状态,显示切片的第一个元素被修改为 10,但长度和容量未变,并且不包含新添加的 4直接传递 slice 后: [10 2 3] 3 3
传递切片指针:
  • modifySlicePointer(&slice)slice 的指针传递给函数。传递指针意味着函数可以直接修改原始切片的长度、容量和内容。
  • modifySlicePointer 函数中,同样首先修改切片的第一个元素为 20,这会直接影响到原始切片。
  • 然后再次调用 append 添加 5,这次因为通过指针传递,原始切片的长度和容量也被修改了。
  • 返回 main 函数后,打印 slice 的状态,显示切片的第一个元素变为 20,并且切片长度增加到 4,包含了新添加的 5传递 slice 指针后: [20 2 3 5] 4 6

图解实例

modifySlice

s[0] = 10          // 修改底层数组的值会影响原始 slice
s = append(s, 4) // 这里的修改不会影响原始 slice 的长度和容量。后续的输出slice不会包含4

对应图示例为:

modifySlicePointer

(*s)[0] = 20     // 修改底层数组的值会影响原始 slice

代码对应图示例为:

*s = append(*s, 5) // 通过指针修改了 slice 的长度和容量

代码对应图示例为:

总结:

  • 直接传递切片:会影响切片的内容(因为共享底层数组),但不会改变切片的长度和容量。
  • 传递切片指针:不仅可以修改切片的内容,还可以改变切片的长度和容量。