认识 Slice | 青训营笔记

214 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

Slice 概述

Golang 中的 Slice(切片)是一种动态数组,在操作时比数组更加灵活,其长度是不固定的,可以进行追加和删除

定义切片

  • 可以声明一个未指定大小的数组来定义切片:var slice []type;,无需说明长度。

  • 使用 make() 函数来创建切片:var slice1 []type = make([]type, len);,简写为slice2 := make([]type, len);

  • 可以在定义时指定容量 capacity (可选):make([]type, len, capacity) len()cap() 返回结果不一定相同:

  • len() :查看数组或 slice 的长度

  • cap():查看数组或 slice 的容量

示例代码:

package main

import "fmt"

func main() {
	var sli_1 []int // 默认 nil切片
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli_1), cap(sli_1), sli_1)

	var sli_2 = []int{} // 空切片
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli_2), cap(sli_2), sli_2)

	var sli_3 = []int{1, 2, 3, 4, 5}
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli_3), cap(sli_3), sli_3)

	sli_4 := []int{1, 2, 3, 4, 5}
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli_4), cap(sli_4), sli_4)

	// 使用make函数定义切片
	var sli_5 []int = make([]int, 5, 8)
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli_5), cap(sli_5), sli_5)

	sli_6 := make([]int, 5)
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli_6), cap(sli_6), sli_6)
}

运行结果:

len=0 cap=0 slice=[]
len=0 cap=0 slice=[]
len=5 cap=5 slice=[1 2 3 4 5]
len=5 cap=5 slice=[1 2 3 4 5]
len=5 cap=8 slice=[0 0 0 0 0]
len=5 cap=5 slice=[0 0 0 0 0]

截取切片

可以通过设置下限及上限来设置截取切片:[lower-bound:upper-bound]。 注意:[] 区间左闭右开

示例:

package main

import "fmt"

func main() {
	sli := []int{1, 2, 3, 4, 5}
	fmt.Printf("len=%d cap=%d slice=%d\n", len(sli), cap(sli), sli)

	// 切指定位置
	fmt.Println("sli[1] == ", sli[1])
	// 从头切到尾
	fmt.Println("sli[:] == ", sli[:])
	// 从1切到最尾部
	fmt.Println("sli[1:] == ", sli[1:])
	// 从最开头切到4(不包含)
	fmt.Println("sli[:4] == ", sli[:4])

	// 子切片从0(包含)到3(不包含) ——> index 0,1,2
	fmt.Println("sli[0:3] == ", sli[0:3])
	fmt.Printf("len=%d cap=%d slic=%v\n", len(sli[0:3]), cap(sli[0:3]), sli[0:3])

	// 子切片从0到3(不包含),4控制切片的cap容量(4-1)
	fmt.Println("sli[0:3:4] == ", sli[0:3:4])
	fmt.Printf("len=%d cap=%d slice=%v\n", len(sli[0:3:4]), cap(sli[0:3:4]), sli[0:3:4])
}

运行结果:

len=5 cap=5 slice=[1 2 3 4 5]
sli[1] ==  2
sli[:] ==  [1 2 3 4 5]
sli[1:] ==  [2 3 4 5]
sli[:4] ==  [1 2 3 4]
sli[0:3] ==  [1 2 3]
len=3 cap=5 slic=[1 2 3]
sli[0:3:4] ==  [1 2 3]
len=3 cap=4 slice=[1 2 3]

追加切片

使用 append(slice,val)追加切片,当容量不够时需要扩容,cap 会翻倍。

示例代码:

package main

import "fmt"

func main() {
	sli := []int{4, 5, 6}
	printSlice(sli)

	// cap不够,翻倍扩容
	sli = append(sli, 7)
	printSlice(sli)

	sli = append(sli, 8)
	printSlice(sli)

	sli = append(sli, 9)
	printSlice(sli)

	// cap不够,翻倍扩容
	sli = append(sli, 10)
	printSlice(sli)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
}

运行结果:

len=3 cap=3 slice=[4 5 6]
len=4 cap=6 slice=[4 5 6 7]
len=5 cap=6 slice=[4 5 6 7 8]
len=6 cap=6 slice=[4 5 6 7 8 9]
len=7 cap=12 slice=[4 5 6 7 8 9 10]

复制切片

Golang 的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果两个数组切片不一样大,就会按照较小的那个数组切片的元素个数进行复制。

copy() 函数的使用格式:copy(destSlice, srcSlice []type) int

  • srcSlice:数据来源切片
  • destSlice:复制的目标(将 srcSlice 复制到 destSlice)
  • 目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致
  • 返回值表示实际发生复制的元素个数

下面的代码展示了使用 copy() 函数将一个切片复制到另一个切片的过程:

 slice1 := []int{1, 2, 3, 4, 5}
 slice2 := []int{5, 4, 3}
 copy(slice2, slice1)          // 只会复制slice1的前3个元素到slice2中
 copy(slice1, slice2)         // 只会复制slice2的3个元素到slice1的前3个位置

虽然通过循环复制切片元素更直接,不过内置的 copy() 函数使用起来更加方便,copy() 函数的第一个参数是要复制的目标 slice,第二个参数是源 slice,两个 slice 可以共享同一个底层数组,甚至有重叠也没有问题。

删除切片

从开头位置删除

删除开头的元素可以直接移动数据指针:

 a := []int {1, 2, 3}
 a := a[1:]      // 删除开头1个元素
 a := a[N:]      // 删除开头N个元素

也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append() 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):

 a := []int {1, 2, 3}
 a := append(a[:0], a[1:]...)         // 删除开头1个元素
 a := append(a[:0], a[N:]...)        // 删除开头N个元素

还可以用 copy() 函数来删除开头的元素,copy()的返回值为实际复制的元素个数:

 a := []int {1, 2, 3}
 // copy(a, a[1:]) 将 a 的第1个元素删除,返回2;a[:1] 刚好是删除完后切片的
 a := a[:copy(a, a[1:])]         // 删除开头1个元素
 a := a[:copy(a, a[N:])]         // 删除开头N个元素

从中间位置删除

对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成:

 a := []int {1, 2, 3, 4, 5, 6, 7, 8} 
 a := append(a[:3], a[3+2:]...)      // 删除中间2个元素
 a := a[:3+copy(a[3:], a[3+2:])]     // 删除中间2个元素

从尾部删除

 a = []int {1, 2, 3}
 a = a[:len(a)-1]          // 删除尾部1个元素
 a = a[:len(a)-N]         // 删除尾部N个元素

删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况。

示例:删除切片指定区间元素

 package main
 ​
 import "fmt"
 ​
 func main() {
     sli := []string{"a", "b", "c", "d", "e"}
 ​
     // 指定删除区间的位置(从哪儿开始删)
     index := 2
 ​
     // 查看删除区间之前的元素和之后的元素,index+2:跳过了从index位置开始的2个元素
     fmt.Println(sli[:index], sli[index+2:])
 ​
     // 将删除区间前后的元素连接起来
     sli = append(sli[:index], sli[index+2:]...)
     fmt.Println(sli)
 }

运行结果:

 [a b] [e]
 [a b e]

代码的删除过程可以使用下图来描述:

图片.png