「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。
什么是切片
- 官方认为:切片是对数组的视图(抽象),一个切片是一个数组片段的描述
- 数组的大小是固定的,而切片则是为数组元素提供动态大小的、灵活的视图;在实践中,切片比数组更常用
- 切片的大小是不固定的,可以追加元素,在追加时可能使切片的容量增大,所以切片也可以叫动态数组
- 切片是一种方便、灵活且强大的包装器
- 切片与数组相比,不需要设定长度,在
[]中不用设定值,相对来说比较自由 - 切片也可以基于现有的切片或数组生成
语法格式
var identifier []type
- 切片通过两个下标来界定,即一个上界和一个下界
- 切片下界的默认值为 0,上界则是该切片的长度。
a[low : high]
和大多数编程语言一样,它也是左闭右开
🌰
a[1:4]
它包含 a 中下标从 1 到 3 的元素
第二个🌰
对于数组var a [10]int来说,以下切片是等价的
a[0:10]
a[:10]
a[0:]
a[:]
简单的🌰
package main
import "fmt"
func main() {
// 数组
primes := [6]int{2, 3, 5, 7, 11, 13}
// 切片
var s []int = primes[1:4]
fmt.Println(s)
}
运行结果
[3 5 7]
切片就像数组的引用
- 切片并不存储任何数据,它只是描述了底层数组中的一段元素
- 更改切片的元素会修改其底层数组中对应的元素
- 与它共享底层数组的切片都会同步这些修改
package main
import "fmt"
func main() {
arrs := [5]int{1, 2, 3, 4, 5}
fmt.Println(arrs)
// 取第一、第二个元素
a := arrs[0:2]
// 取第二、第三个元素
b := arrs[1:3]
fmt.Println(a, b)
// 修改切片 b 的第一个元素
b[0] = 100
fmt.Println(a, b)
fmt.Println(arrs)
}
运行结果
[1 2 3 4 5]
[1 2] [2 3]
[1 100] [100 3]
[1 100 3 4 5]
可以看到底层数组 names 和切片 a 都同步修改了对应的值
原理图
- a、b 两个切片只是 arrs 数组的视图,切片下标指向的是底层数组对应的值
- 所以修改 b[0] 等同于修改 arrs[1],也会同步修改 a[1],因为它也指向 arrs[1]
Reslice
- 就是对切片进行切片
- 切片保存了对底层数组的引用,若将某个切片赋予另一个切片,它们会指向(引用)同一个数组
package main
import "fmt"
func main() {
// 切片 s 的底层数组是:[1, 2, 3, 4, 5, 6]
s := []int{1, 2, 3, 4, 5, 6}
s = s[1:4]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[1:]
fmt.Println(s)
}
运行结果
[2 3 4]
[2 3]
[3]
原理图
s 不断对自己进行切片,但他们都是底层数组[1, 2, 3, 4, 5, 6]的视图
slice 的实现
slice 像一个结构体
- ptr:指向数组第一个元素的指针
- len:切片长度,切片中的元素数量,通过切片下标取值只能取 len 以内的值
[0, len(s) -1]
- cap:切片容量,底层数组的元素数量(从切片指针 ptr 开始)
slice 的扩展
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Println(s1, s2)
}
运行结果
[2 3 4 5] [5 6]
疑问?
为什么 s1 只有四个元素而且没有 6,但是 s2 取第五个元素的时候没有报错,而且返回的是 6?
难道可以直接 s1[4] 来取值?
如果直接打印 s1[4] 会报错
panic: runtime error: index out of range [4] with length 4
goroutine 1 [running]:
main.main()
/tmp/sandbox1066338689/prog.go:10 +0x71
原理图
- 上面也讲了 slice 的实现原理
- 核心:slice 可以向后扩展,只要不超过底层数组/切片的 cap 就可以继续取值
s1 := arr[2:6]:取的值是 2-5,对应的下标是 0-3,但底层数组后面仍然有值,若有需要 s1 可以扩展取值第 4、5 位元素;所以 s1 的 len 是 4,cap 是 6s2 := s1[3:5]:s1 并没有第 5 位元素,所以 s2 要进行扩展,就会取到底层数组对应位置的值,就是 6;所以 s2 的 len 是 2,cap 是 3
看看 s1、s2 的 len、cap
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
fmt.Printf("s1=:%v, len: %v, cap: %v \n", s1, len(s1), cap(s1))
fmt.Printf("s2=:%v, len: %v, cap: %v", s2, len(s2), cap(s2))
}
运行结果
s1=:[2 3 4 5], len: 4, cap: 6
s2=:[5 6], len: 2, cap: 3
总结
- slice 可以向后扩展,不可以向前扩展
- s[i] 最大不可以超越 len(s)
- 向后扩展不可以超越底层数组/切片的 cap(s)
package main
import "fmt"
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6]
s2 := s1[3:5]
// s1 的 cap 是 6,所以上边界最大取 6
s3 := s1[3:6]
// s2 的 cap 是 3,所以上边界最大取 3
s4 := s2[1:3]
}
空切片
- 切片的零值是 nil
- nil 切片的长度和容量为 0 且没有底层数组
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
运行结果
[] 0 0
nil!