卷毛0基础学习Golang-基础-slice切片
持续更新中----
切片简述
数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。Go语言提供了数组切片(slice)来弥补数组的不足。
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。
切片并不是数组或数组指针,它通过内部指针和相关属性引⽤数组⽚段,以实现变⻓⽅案。
slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。
创建切片
slice和数组的区别:声明数组时,[ ]内写明了数组的长度或使用…自动计算长度,而声明slice时,[ ]内没有任何字符。经常使用的切片创建方法:
- 自动推导类型创建slice
s1 := [] int {1, 2, 3, 4} 创建 有 4 个元素的切片,分别为:1234 - 借助make创建 slice,格式:make(切片类型,长度,容量)
s2 := make([]int, 5, 10) len(s2) = 5, cap(s2) = 10 - make时,没有指定容量,那么 长度==容量
s3 := make([]int, 5) len(s3) = 5, cap(s3) = 5
func main() {
s1 := [] int {1, 2, 3, 4} // 创建 有4个元素的切片
fmt.Println("s1=", s1)
s2 := make([]int, 5, 10) // 借助make创建 slice,格式:make(切片类型,长度,容量)
s2[4] = 7
//s2[5] = 9 // 报错:panic: runtime error: index out of range
fmt.Println("s2=", s2)
fmt.Printf("len(s2)=%d, cap(s2)=%d\n", len(s2), cap(s2))
s3 := make([]int, 5) // make时,没指定容量,那么 长度 == 容量
s3[2] = 3
fmt.Println("s3=", s3)
fmt.Printf("len(s2)=%d, cap(s2)=%d\n", len(s3), cap(s3))
}
注意:make只能创建slice、map和channel,并且返回一个有初始值(非零)的对象。
切片操作
切片截取
操作 | 含义 |
---|---|
s[n] | 切片s中索引位置为n的项 |
s[:] | 从切片s的索引位置0到len(s)-1处所获得的切片 |
s[low:] | 从切片s的索引位置low到len(s)-1处所获得的切片 |
s[:high] | 从切片s的索引位置0到high处所获得的切片,len=high |
s[low:high] | 从切片s的索引位置low到high处所获得的切片,len=high-low |
s[low : high : max] | 从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low |
len(s) | 切片s的长度,总是<=cap(s) |
cap(s) | 切片s的容量,总是>=len(s) |
截取可表示为s[low:high:max]。low:表示下标的起点。 high:表示下标的终点(左闭右开,不包括此下标)。 长度 len = high – low。容量 cap = max – low。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len()和cap() 函数分别返回slice的长度和容量。
示例说明:
array := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
Column 1 | Column 2 | Column 2 | Column 2 | Column 2 |
---|---|---|---|---|
array[:6:8] | [0 1 2 3 4 5] | 6 | 8 | 省略 low |
array[5:] | [5 6 7 8 9] | 5 | 5 | 省略 high、 max |
array[:3] | [0 1 2] | 3 | 10 | 省略 high、 max |
array[:] | [0 1 2 3 4 5 6 7 8 9] | 10 | 10 | 全部省略 |
切片和底层数组关系
func main() {
arr := [] int {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := arr[2:5] // 从arr[2]开始,取 5-2 个元素,组成切片s1。
fmt.Println("s1=", s1) // s1= [2 3 4]
s1[1] = 666 // 这样将arr数组中 3 --> 666。
fmt.Println("arr=", arr) // arr= [0 1 2 666 4 5 6 7 8 9]
s2 := s1[2:7] // 从s1[2]开始, 取 7-2 个元素,组成 s2。
fmt.Println("s2=", s2) // 实际上还是取的 数组arr。 s2= [4 5 6 7 8]
s2[2] = 777 // 这会将arr中的 6 --> 777
fmt.Println("arr=", arr) // arr= [0 1 2 666 4 5 777 7 8 9]
}
利用数组创建切片。切片在操作过程中,是直接操作原数组。切片是数组的引用!因此,在go语言中,我们常常使用切片代替数组。
切片做函数参数
切片作为函数参数时,传引用。
func testFunc(s []int) { // 切片做函数参数
s[0] = -1 // 直接修改 main中的 slice
}
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(slice)
testFunc(slice) // 传引用
fmt.Println(slice)
}
常用操作函数
append函数
append() 函数可以向 slice 尾部添加数据,可以自动为切片扩容。常常会返回新的 slice 对象:
var s1 []int //创建nil切片,或者:s1 := make([]int, 0)
s1 = append(s1, 1) //追加1个元素
s1 = append(s1, 2, 3) //追加2个元素
s1 = append(s1, 4, 5, 6) //追加3个元素
fmt.Println(s1) //[1 2 3 4 5 6]
s2 := make([]int, 5)
s2 = append(s2, 6)
fmt.Println(s2) //[0 0 0 0 0 6]
s3 := []int{1, 2, 3}
s3 = append(s3, 4, 5)
fmt.Println(s3) //[1 2 3 4 5]
append函数会智能的将底层数组的容量增长,一旦超过原底层数组容量,通常以2倍(1024以下)容量重新分配底层数组,并复制原来的数据。因此,使用append 给切片做扩充时,切片的地址可能发生变化。但,数据都被重新保存了,不影响使用。
func main() {
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 100; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
}
输出结果如下:
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
cap: 64 -> 128
copy函数
函数 copy 在两个 slice 间复制数据,复制⻓度以 len 小的为准,两个 slice 指向同⼀底层数组。直接对应位置覆盖。
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := data[8:] //{8, 9}
s2 := data[:5] //{0, 1, 2, 3, 4}
copy(s2, s1) // dst:s2, src:s1
fmt.Println(s2) //[8 9 2 3 4]
//两个 slice 指向同⼀底层数组。直接对应位置覆盖。
fmt.Println(data) //[8 9 2 3 4 5 6 7 8 9]