切片 Slice
切片,并不是数组或数组指针,它通过内部指针和相关属性引用数组片段,以实现变长方案。
- 切片是数组的一个引用,因此切片是引用类型,但自身是结构体,值拷贝传递
- 切片的长度是可以改变的,因此,切片是一个可变的数组
- 切片的遍历方式和数组一样,可以用len()求长度,表示可用元素数量,读写操作不能超过该限制
- cap可以求出slice最大的扩张容量,不能超出数组限制,0 <= len(slice) <= len(array)
- 定义:var 变量名 []类型
- 如果 slice == nil,那么 len,cap 结果都是 0
创建切片的几种方式:
package main import ( "fmt" ) func main() { var s1 []int // 声明 s2 := []int{} // 声明+赋值,赋值为空 var s3 []int = make([]int,5) // len = cap = 5 var s4 []int = make([]int,2,5) // type len cap s5 := []int{1,2,3} // 声明+赋值 arr := [5]int{1,2,3,4,5} // 对数组进行切片 var s6 []int s6 = arr[1:4] }
切片初始化
package main import ( "fmt" ) var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} var slice0 []int = arr[2:8] var slice1 []int = arr[0:6] //可以简写为 var slice []int = arr[:end] var slice2 []int = arr[5:10] //可以简写为 var slice[]int = arr[start:] var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:] var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素 func main() { arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0} slice5 := arr[2:8] slice6 := arr[0:6] //可以简写为 slice := arr[:end] slice7 := arr[5:10] //可以简写为 slice := arr[start:] slice8 := arr[0:len(arr)] //slice := arr[:] slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素 }
通过make,创建切片
package main import ( "fmt" ) // 全局 var slice0 []int = make([]int,5) // [0 0 0 0 0] var slice1 = make([]int,5) // [0 0 0 0 0] var slice2 = make([]int,2,5) // [0 0] func main() { // 局部 slice3 := make([]int,5) // [0 0 0 0 0] slice4 := make([]int,2,5) // [0 0] }
读写操作,实际目标是底层数组,只需要注意索引的差别
package main import ( "fmt" ) func main() { data := [...]int{0,1,2,3,4,5} s := data[2:4] s[0] += 100 s[1] += 200 fmt.Println(s) // [102 203] fmt.Println(data) // [0 1 102 203 4 5] }
直接创建slice对象,自动分配底层数组
package main import "fmt" func main() { s1 := []int{0, 1, 2, 3, 8: 100} // 索引为8,共9个元素 fmt.Println(s1, len(s1), cap(s1)) // [0 1 2 3 0 0 0 0 100] 9 9 s2 := make([]int, 6, 8) // len = 6 , cap = 8 fmt.Println(s2, len(s2), cap(s2)) // [0 0 0 0 0 0] 6 8 s3 := make([]int, 6) // len = cap = 6 fmt.Println(s3, len(s3), cap(s3)) // [0 0 0 0 0 0] 6 6 }
使用make动态创建slice,避免了数组必须用常量做长度的麻烦。还可以用指针直接访问底层数组,退化成普通数组操作。
package main import "fmt" func main() { s := []int{0,1,2,3} p := &s[2] // 获取数组元素的指针 *p += 100 // 重新赋值 // s[2] += 100 fmt.Println(s) // [0 1 102 3] }
[][]T,是指 元素类型为 []T
package main import "fmt" func main() { data := [][]int{ []int{1,2,3}, []int{100,200}, []int{11,22,33,44}, } fmt.Println(data) // [[1 2 3] [100 200] [11 22 33 44]] }
直接修改 struct array/slice 成员
package main import "fmt" func main() { d := [5]struct{ x int }{} s := d[:] d[1].x = 10 s[2].x = 20 fmt.Println(d) // [{0} {10} {20} {0} {0}] fmt.Printf("%p,%p,\n",&d,&d[0]) // 0xc042066030,0xc042066030, }
切片基本操作,增删改查
package main import "fmt" func main() { // 增 var a = []int{1,3,5} var b = []int{2,4,6} c := append(a,b...) // append 追加数据 fmt.Println(c) // [1 3 5 2 4 6] d := append(c,9) fmt.Println(d) // [1 3 5 2 4 6 9] // 删 用append,以另一种形式实现删除操作 d = append(d[:3],d[4:]...) fmt.Println(d) // [1 3 5 4 6 9] 删除第四个元素 // 改 // 用append删除一个元素,再用append增加一个元素 // 查 fmt.Println(d[1:3]) // [3 5] // 清空 d = nil fmt.Println(d) // [] }
向切片中添加元素,生成新切片
package main import ( "fmt" ) func main() { s1 := make([]int, 0, 5) fmt.Printf("%p\n", &s1) // 0xc0420443a0 s2 := append(s1, 1) fmt.Printf("%p\n", &s2) // 0xc0420443e0 fmt.Println(s1, s2) // [] [1] }
超出原切片cap限制,就会重新分配底层数组,即便原数组并未填满。
package main import ( "fmt" ) func main() { data := [...]int{0, 1, 2, 3} s := data[:2:3] // 取data前两个元素,并设置cap为3 s = append(s, 100, 200) // append 两个值,超出 s.cap 限制。重新分配底层数组,与原数组无关。 fmt.Println(s, data) // [0 1 100 200] [0 1 2 3] fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。 0xc04200a270 0xc042008320 }
从输出结果可以看出,append后的s重新分配了底层数据,并复制数据。如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。通常以2倍容量重新分配底层数组。
在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制的开销。或初始化足够长的len属性,改用索引号进行操作。及时释放不再使用的slice对象,避免持有过期数组,造成GC无法回收。
slice中cap重新分配规律:以2倍容量进行重新分配
package main import ( "fmt" ) func main() { s := make([]int, 0, 1) c := cap(s) for i := 0; i < 50; 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
遍历
package main import "fmt" func main() { data := []int{10, 20, 30, 40, 50, 60, 70, 80, 90} for index,val := range data { fmt.Println(index,val) } }
调整大小
package main import "fmt" func main() { var a = []int{1, 3, 4, 5} fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a)) b := a[1:2] fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b)) c := b[0:3] fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c)) } // slice a : [1 3 4 5] , len(a) : 4 // slice b : [3] , len(b) : 1 // slice c : [3 4 5] , len(c) : 3
数组与切片,内存分布
字符串与切片
package main import "fmt" func main() { str := "hello world" // 字符串切片 s1 := str[:5] fmt.Println(s1) // 字符串 修改 s2 := []byte(str) //中文字符需要用[]rune(str) s2[0] = 'H' s2 = append(s2,'!') s3 := string(s2) fmt.Println(s3)
切片:的理解
data[6:8],取6和7,长度len=2,最大可扩容长度cap=4
data[:6:8],内容为0-5,长度len=6,cap=8