介绍

切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数append来实现的,这个函数可以快速且高效的增长切片。还可以通过对切片再次切片来缩小一个切片的大小。
因为切片底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。
切片是一个数组的引用,切片是引用类型

内部实现

切片是有3个字段的数据结构,分别是:指向底层数组的指针、切片可访问元素的个数(长度)、切片允许增长到的元素个数(容量)。一个切片(地址)占用24字节内存

定义切片

整数切片
var slice []int

1、让切片引用一个已经创建好的数组

直接引用数组,这个数组事先存在,程序员可见

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 定义数组
  5. var intArr [5]int = [...]int{1, 2, 3, 4, 5}
  6. // 以intArr为底层数组,创建切片
  7. slice := intArr[2:4] // 长度为 4-2=2 , 容量为 intArr底层数组的容量5 - 2 = 3
  8. fmt.Println("slice 元素 = ", slice) // 3, 4
  9. fmt.Println("slice 长度 = ", len(slice)) // 2
  10. fmt.Println("slice 容量 = ", cap(slice)) // 3
  11. }

2、通过make创建切片

会自动创建底层数组并维护,对外不可见
var 切片名 []type = make([]type, len, [cap])
默认零值

  1. var slice []int = make([]int, 3) // 类型: []int, 值: [0 0 0]
  2. // 创建一个整形切片
  3. // 长度是3个元素,容量是5个元素
  4. slice := make([]int, 3, 5)
  5. // 创建一个字符串切片
  6. // 长度和容量都是5个元素
  7. slice := make([]string, 5)

func make(Type, size IntegerType) Type
内建函数make分配并初始化一个类型为切片、映射、或通道的对象。其第一个实参为类型,而非值。make的返回类型与其参数相同,而非指向它的指针。其具体结果取决于具体的类型:

  • 切片:size指定了其长度。该切片的容量等于其长度。切片支持第二个整数实参可用来指定不同的容 量; 它必须不小于其长度,因此 make([]int, 0, 10) 会分配一个长度为0,容量为10的切片。
  • 映射:初始分配的创建取决于size,但产生的映射长度为0。size可以省略,这种情况下就会分配一个 小的起始大小。
  • 通道:通道的缓存根据指定的缓存容量初始化。若 size为零或被省略,该信道即为无缓存的。

nil和空切片

  1. 只声明不初始化,就会创建一个nil切片

使用场景:要求返回一个切片,但是发生异常的时候,可以返回一个nil切片

  1. // 创建nil切片
  2. var slice []int
  3. // nil切片
  4. var slice01 []int
  5. fmt.Printf("slice01 类型: %T, 值: %v \n", slice01, slice01) // slice01 类型: []int, 值: []
  6. fmt.Println(slice01 == nil) // true
  1. 声明空切片

使用场景:数据库查询返回0个查询结果时

  1. // 使用make创建空的整形切片
  2. slice := make([]int, 0)
  3. // 使用切片字面量创建空的整形切片
  4. slice := []int{}

切片可以继续切片

  1. // 切片,修改
  2. var slice05 []int = []int{5, 6, 7, 8, 9}
  3. fmt.Println("slice05 = ", slice05) // [5 6 7 8 9]
  4. slice06 := slice05[2:4]
  5. fmt.Println("slice06 = ", slice06) // [7 8]
  6. slice06[0] = 22
  7. fmt.Println("修改slice06后: slice05 = ", slice05) // slice05也发生改变 [5 6 22 8 9]
  8. fmt.Println("修改slice06后: slice06 = ", slice06) // [22 8]

计算切片长度和容量

对于底层数组容量是k的切片 slice[i:j] 来说
长度:j - i
容量:k - i
注意: j不能超出原始数组的长度,否则报错
panic: runtime error: slice bounds out of range [:6] with capacity 5

  1. slice[0:]
  2. slice[:]
  3. slice[0:len()]
  4. var slice05 []int = []int{5, 6, 7, 8, 9}
  5. fmt.Println("slice05 = ", slice05)
  6. slice06 := slice05[2:6] // panic: runtime error: slice bounds out of range [:6] with capacity 5
  7. fmt.Println("slice06 = ", slice06)

切片内存分配探究

image.png
slice从底层来说是一个数据结构,struct结构体

  1. type slice struct {
  2. ptr *[2]int
  3. len int
  4. cap int
  5. }

首地址,指向底层数组,len和cap指定了底层数组的长度和容量

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 定义数组
  5. var intArr [5]int = [...]int{10, 20, 30, 40, 50}
  6. fmt.Println("intArr = ", intArr) // [10 20 30 40 50]
  7. // 以intArr为底层数组,创建切片
  8. slice := intArr[2:4] // 长度为 4-2=2 , 容量为 intArr底层数组的容量5 - 2 = 3
  9. fmt.Println("slice 元素 = ", slice) // [30 40]
  10. fmt.Println("slice 长度 = ", len(slice)) // 2
  11. fmt.Println("slice 容量 = ", cap(slice)) // 3
  12. // 修改切片元素,会作用到底层数组
  13. slice[1] = 21
  14. fmt.Println("修改后slice 元素 = ", slice) // [30 21]
  15. fmt.Println("修改后intArr = ", intArr) // [10 20 30 21 50]
  16. }

切片遍历

1、常规for循环遍历

  1. // 常规for循环遍历切片
  2. var slice04 []int = []int{10, 20, 30}
  3. for i := 0; i < len(slice04); i++ {
  4. fmt.Printf("slice04[%v] = %v, 地址:%p \n", i, slice04[i], &slice04[i])
  5. }
  6. slice04[0] = 10, 地址:0xc000012198
  7. slice04[1] = 20, 地址:0xc0000121a0
  8. slice04[2] = 30, 地址:0xc0000121a8

2、for range遍历

range创建每个元素的副本,而不是直接返回对该元素的引用
迭代返回变量是一个迭代过程中根据切片依次赋值的新变量
所以v的地址总是相同的
要想获取每个元素的地址,可以使用切片变量和索引值

  1. // range创建每个元素的副本,而不是直接返回对该元素的引用
  2. // 迭代返回变量是一个迭代过程中根据切片依次赋值的新变量
  3. // 所以v的地址总是相同的
  4. // 要想获取每个元素的地址,可以使用切片变量和索引值
  5. for i, v := range slice04 {
  6. fmt.Printf("range - slice04[%v] = %v, 地址:%p \n", i, v, &v)
  7. }
  8. range - slice04[0] = 10, 地址:0xc000014100
  9. range - slice04[1] = 20, 地址:0xc000014100
  10. range - slice04[2] = 30, 地址:0xc000014100

增长切片

func append(slice []Type, elems …Type) []Type
内建函数append将元素追加到切片的末尾。若它有足够的容量,其目标就会重新切片以容纳新的元素。否则,就会分配一个新的基本数组,将被引用的现有的值复制到新数组里,再追加新的值。
append返回更新后的切片,因此必须存储追加后的结果。

  1. // append 追加
  2. var slice07 = make([]int, 3, 5) // 长度3,容量5
  3. fmt.Println("slice07 = ", slice07, "slice07容量", cap(slice07))
  4. // slice07 = [0 0 0] slice07容量 5
  5. // 追加元素
  6. slice07 = append(slice07, 2)
  7. fmt.Println("slice07 = ", slice07) // slice07 = [0 0 0 2]
  8. // 追加切片 slice...
  9. slice07 = append(slice07, slice07...)
  10. fmt.Println("slice07 = ", slice07, "追加后slice07容量", cap(slice07))
  11. // slice07 = [0 0 0 2 0 0 0 2] 追加后slice07容量 10

函数append会智能的处理底层数组的容量增长。再切片容量小于1000个元素时,总是会成倍的增长容量,一旦元素个数超过1000,容量的增长因子会设为1.25,也就是每次增加25%的容量。随着语言的演化,增长算法可能会有所改变。

切片的拷贝操作

使用 copy 内置函数完成拷贝
func copy(dst, src []Type) int
内建函数copy将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中。copy返回被复制的元素数量,它会是 len(src) 和 len(dst) 中较小的那个。来源和目标的底层内存可以重叠。
copy(para1, para2) 参数的数据类型是切片
para1 是目标切片

  1. // 拷贝切片
  2. var a []int = []int{1, 2, 3}
  3. var b = make([]int, 5) // 长度 容量都是5
  4. c := copy(b, a) // 返回被复制的元素个数,
  5. fmt.Println("c = ", c)
  6. fmt.Println("a = ", a, "容量=", cap(a)) // a = [1 2 3] 容量= 3
  7. fmt.Println("b = ", b, "容量=", cap(b)) // b = [1 2 3 0 0] 容量= 5
  8. c := copy(a, b)
  9. // c = 3
  10. // a = [0 0 0] 容量= 3
  11. // b = [0 0 0 0 0] 容量= 5

切片引用

  1. package main
  2. import "fmt"
  3. // 引用类型
  4. func test(slice []int) {
  5. slice[0] = 100
  6. }
  7. func main() {
  8. var slice = []int{1, 2, 3}
  9. fmt.Println("slice = ", slice) // slice = [1 2 3]
  10. test(slice)
  11. fmt.Println("slice = ", slice) // slice = [100 2 3]
  12. }

字符串切片

string底层是一个 byte 数组
image.png
切片用来截取字符串

  1. package main
  2. import "fmt"
  3. func main() {
  4. // string底层是一个 byte 数组,因此string也可以进行切片
  5. str := "xiaobaobao"
  6. // 使用切片 获取baobao
  7. slice := str[4:]
  8. fmt.Printf("slice类型: %T, 值: %v \n", slice, slice)
  9. }
  10. // slice类型: string, 值: baobao

string是不可改变的,也就是说通过string[i] = ‘x’是不行的

  1. str1 := "小笑笑"
  2. arr1 := []rune(str1)
  3. fmt.Printf("arr1类型: %T, 值: %v \n", arr1, arr1)
  4. arr1[1] = '涛'
  5. arr1[2] = '涛'
  6. str3 := string(arr1)
  7. fmt.Printf("str3类型: %T, 值: %v \n", str3, str3)
  8. // arr1类型: []int32, 值: [23567 31505 31505]
  9. // str3类型: string, 值: 小涛涛