Go 切片使用和底层原理 | 青训营;

104 阅读3分钟

今天学习了Go语言基础语法,其中数组和切片部分与其他语言不同,需要重点理解、输入探究。资料来自《Go 语言之旅》

数组

理解 Go 切片需要首先理解 Go 数组,本质上与 C 中的数组区别不大:

  • 定义时需要长度,且长度必须是常量
  • 定义后不能改变大小 除了在语法上([]符号需要放在类型前,类型需要放在变量后)不同之外,区别不大

切片

Go 中的切片可以理解为:数组的引用:

  • 切片并不存储任何数据,它只是描述了底层数组中的一段。
  • 更改切片的元素会修改其底层数组中对应的元素。
  • 与它共享底层数组的切片都会观测到这些修改。

切片的使用

  • 可用 []T 来表示元素类型为 T 的切片类型
  • 切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
    a[low : high]
    
    它会选择一个半开区间,包括第一个元素,但排除最后一个元素。

切片的默认行为

在进行切片时,你可以利用它的默认行为来忽略上下界。
切片下界的默认值为 0,上界则是该切片的长度。
对于数组

var a [10]int

来说,以下切片是等价的:

a[0:10]
a[:10]
a[0:]
a[:]

切片的长度与容量

  • 长度:它所包含的元素个数。可通过len(s)获取
  • 容量:从它的第一个元素开始数,到其底层数组元素末尾的个数。可通过cap(s)获取。(注意不是底层数组的长度!

通过几个例子理解:

	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]

	// 截取切片使其长度为 0
	s = s[:0]
	printSlice(s)  // len=0 cap=6 []

	// 拓展其长度
	s = s[:4]
	printSlice(s)  // len=4 cap=6 [2 3 5 7]

	// 舍弃前两个值
	s = s[2:]
	printSlice(s) // len=2 cap=4 [5 7]  **这里cap发生变化**
	
	// 超出范围
	s = s[:5]
	printSlice(s) // panic: runtime error: slice bounds out of range [:5] with capacity 4

nil 切片

切片的零值是 nil。 nil 切片的长度和容量为 0 且没有底层数组。

用 make 创建切片

make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

a := make([]int, 5)  // len(a)=5

要指定它的容量,需向 make 传入第三个参数:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

值得一提的是,make 虽然是将数据存在堆上,但与 new 不同的是,只适用于 chan slice map 三种内置类型,且返回的并非指针而是这个类型本身。

向切片追加元素

为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。

func append(s []T, vs ...T) []T

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

append 的结果是一个包含原切片所有元素加上新添加元素的切片。

当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。