切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。 ## 数组生成切片 切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。 从连续内存区域生成切片是常见的操作,格式如下: `slice [开始位置 : 结束位置]`,切片结果不包括结束位置 从数组生成切片: ``` package main import "fmt" func main() { var a = [5]int{1, 2, 3, 4, 5} fmt.Println(a[1:3]) // [2 3] } ``` 从数组或切片生成新的切片拥有如下特性: - 切片位置为:0 - len(数组); - 取出元素不包含结束位置对应的索引; - 当缺省开始位置时,表示从连续区域开头到结束位置; - 当缺省结束位置时,表示从开始位置到整个连续区域末尾; - 两者同时缺省时,与切片本身等效; - 两者同时为 0 时,等效于空切片,一般用于切片复位。 ## 直接声明新的切片 除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:var name []Type,其中 name 表示切片的变量名,Type 表示切片对应的元素类型。 ``` package main import "fmt" func main() { // 声明字符串切片 var strList []string // 声明整型切片 var numList []int // 声明一个空切片 var numListEmpty = []int{} // 输出3个切片 fmt.Println(strList, numList, numListEmpty) // [] [] [] // 输出3个切片大小 fmt.Println(len(strList), len(numList), len(numListEmpty)) // 0 0 0 // 切片判定空的结果 fmt.Println(strList == nil) // true fmt.Println(numList == nil) // true fmt.Println(numListEmpty == nil) // false } ``` **切片是动态结构,只能与 nil 判定相等,不能互相判定相等。**声明新的切片后,可以使用 append() 函数向切片中添加元素。 ## 使用 make() 函数构造切片 如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下: `make( []Type, size, cap )` 其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。 ``` package main import "fmt" func main() { a := make([]int, 2) b := make([]int, 2, 10) fmt.Println(a, b) // [0 0] [0 0] fmt.Println(len(a), len(b)) // 2 2 } ``` 其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。 容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。 使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。 ## append() Go语言的内建函数 append() 可以为切片动态添加元素 ``` package main import "fmt" func main() { var a []int a = append(a, 1) // 追加1个元素 a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式 a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包 fmt.Println(a) // [1 1 2 3 1 2 3] } ``` 除了在切片的尾部追加,我们还可以在切片的开头添加元素 ``` package main import "fmt" func main() { var a = []int{1,2,3} a = append([]int{0}, a...) // 在开头添加1个元素 a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片 fmt.Println(a) // [-3 -2 -1 0 1 2 3] } ``` 在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。 ## 从切片中删除元素 Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。 **删除头部元素** ``` package main import "fmt" func main() { slice := []int{1, 2, 3} slice = slice[1:] // 删除开头1个元素 fmt.Println(slice) // [2 3] } ``` 也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化) ``` package main import "fmt" func main() { slice := []int{1, 2, 3} slice = append(slice[:0], slice[1:]...) // 删除开头1个元素 fmt.Println(slice) // [2 3] } ``` **删除尾部元素** ``` package main import "fmt" func main() { // 删除尾部一个元素 slice := []int{1, 2, 3, 4, 5, 6} // slice = slice[:len(slice)-1] slice = append(slice[:len(slice)-1], slice[:0]...) fmt.Println(slice) } ``` **删除任意位置元素** ``` package main import "fmt" func main() { slice := []int{1, 2, 3, 4, 5, 6} index := 3 slice = append(slice[:index], slice[index+1:]...) fmt.Println(slice) } ``` ## 切片随机取出元素 ``` package main import ( "fmt" "time" "math/rand" ) func main(){ unPatientIdList := []int{1, 2, 3, 4} rand.Seed(time.Now().Unix()) randomNum := rand.Intn(len(unPatientIdList)) // 取出的范围0-3 fmt.Println(unPatientIdList[randomNum]) } ``` ``` package main import "fmt" func main() { a := []int{1, 2, 3, 4, 5} b := a[1:4] c := b[2:4] fmt.Println(c) // [4 5] } ``` **Reslice** - Reslice时索引以被slice的切片为准 - 索引不可以超过被slice的切片的容量cap()值 - 索引越界不会导致底层数组的重新分配而是引发错误 **Append** - 可以在slice 尾部追加元素 - 可以将一个slice追加在另一个slice尾部 - 如果最终长度未超过追加到slice的容量则返回原始slice - 如果超过追加到的slice的容量则将重新分配数组并拷贝原始数据