6-切片
切片是一个拥有先用数据类型元素的可变长度的序列。
数组是固定长度,切片是可变长度
数组是值类型,切片是引用类型
数组有很多局限性,切片非常灵活,支持自动扩容
切片内部结构包含 地址
、长度
、容量
,一般用于快速操作一块数据集合。
创建切片
注意:切片是引用类型!!!
// 切片
var identifier []type
var identifier = []type{initial value}
identifier := []type{initial value}
// 数组
var identifier [len]type
var identifier = [...]type{initial value}
identifier
:切片名、type
:切片数据类型
区别于数组,切片在定义时不用填写 len。
它和初始化数组时省略 len 不同,数组省略 len 时要写...
,切片啥也不用写。
func main() {
var a []string // 声明一个字符串切片
var b = []int{1, 2, 3, 4, 5} // 声明一个整型切片,并初始化
c := []bool{false, true} // 声明一个布尔型切片,并初始化
fmt.Println(a) // []
fmt.Println(b) // [1 2 3 4 5]
fmt.Println(c) // [false, true]
}
基于数组创建
切片底层是数组,当底层数组不够的时候,切片就会扩容。
上面的定义是创建一个匿名数组,让切片指向这个匿名数组,下面是基于数组定义切片
identifier := array[start_with:end]
func main() {
// 基于数组定义切片
arr := [8]int{55, 56, 57, 58, 59, 60, 61, 62}
b := arr[1:5] // 其范围用数学表示为:[1,5) 从arr[1]取到arr[4]不包含arr[5]
fmt.Println(b) // [55 56 57 58 59]
fmt.Printf("%T \n", b) // []int
// 切片再次切片
c := b[0:len(b)] // len(b)为5,所以取了b[0]、b[1]、b[2]、b[3]、b[4],相当于复制一整个切片
fmt.Println(c) // [55 56 57 58 59]
fmt.Printf("%T \n", c) // []int
}
直接定义切片和指定数组定义切片的区别在于:
- 直接定义切片会引用一个匿名数组,指定数组定义切片会引用指定数组
- 直接定义切片
长度=容量
,指定数组定义切片长度和容量视具体情况
直接定义切片 ↓
指定数组定义切片 ↓
容量指的是从接片第一个元素到底层数组的最后一个元素
例如上面第三张图中:arr := [8]int{55, 56, 57, 58, 59, 60, 61, 62} c := arr[3:6] len(c) // 3 cap(c) // 5
使用make()创建
如果需要动态的创建一个切片,可以使用内置的make()
函数
基本语法为:
make([]type, len, cap)
var identifier []type = make([]type, len, cap)
identifier := make([]type, len, cap)
type
:切片数据类型、len
:长度、cap
:容量
cap 可以不填,默认和 len 相同
eg:
func main() {
// make函数构造切片
// make([]type, len, cap)
d := make([]int, 4, 10) // 构造一个整型切片,填充5个元素,最大容量10
fmt.Println(d) // [0 0 0 0]
fmt.Printf("%T \n", d) // []int
fmt.Println(len(d)) // 4
fmt.Println(cap(d)) // 10
}
动态就动态在于它能使用变量哈哈哈
func fn(a int, b int) []int {
return make([]int, a, b)
}
切片是引用类型
判空
检查切片是否为空,不能用 s == nil
,而是应该使用 len(s) == 0
切片是一种引用类型,当它被声明的时候,没有指向任何数组,包括匿名数组也没有,此时切片中指针为
nil
var s []int // s == nil
当切片被初始化的时候,它就指向了一个数组,这时切片中的指针不为 nil
。
var s = []int{} // s != nil
切片不能直接比较
切片是一种引用类型,我们不能用 ==
操作符来判断两个切片是否含有全部相等元素。
切片唯一合法的比较操作是和 nil
比较。
一个nil
值的切片并没有底层数组,一个nil
值的切片的长度和容量都是0。
但是我们不能说一个长度和容量都是0的切片一定是nil
var s1 []int //len(s1)=0; cap(s1)=0; s1==nil
s2 := []int{} //len(s2)=0; cap(s2)=0; s2!=nil
s3 := make([]int, 0) //len(s3)=0; cap(s3)=0; s3!=nil
所以要判断一个切片是否是空的,要是用len(s) == 0
来判断,不应该使用s == nil
来判断。
切片的拷贝赋值
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
func main() {
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
}
切片遍历
切片的遍历方式和数组是一致的,支持索引遍历和for range
遍历。
func main() {
s := []int{1, 3, 5}
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
for index, value := range s {
fmt.Println(index, value)
}
}
append()
Go 中的内建函数 append()
可以为切片动态添加元素,可以一次添加一个或多个元素。
append(slice, elem_arr_or_slice)
eg:
func main() {
s := []int{1, 2, 3, 4, 5} // [1 2 3 4 5]
s = append(s, 11) // [1 2 3 4 5 11]
s = append(s, 12, 13, 14) // [1 2 3 4 5 11 12 13 14]
}
append()
第二个参数也可以是另一个切片,不过记得加上...
func main() {
s2 := []int{55, 56, 57}
s = append(s, s2...)
}
注意: 通过var声明的零值切片可以在 append()
函数直接使用,无需初始化。
// 正确
var s []int
s = append(s, 1, 2, 3)
// X 没必要
var s = []int{}
s = append(s, 1, 2, 3)
// X 没必要
var s = make([]int)
s = append(s, 1, 2, 3)
copy()
切片是引用类型,如果直接 s1 = s2
,其实是将 s2 中的数组地址赋值给 s1 的指针,s1 修改的时候 s2 也会受影响。
要实现真正的复制,需要使用内建函数 copy()
进行复制。
copy(destSlice, srcSlice)
destSlice
:目标切片、srcSlice
:数据来源切片
func main() {
s1 := []int{1, 2, 3, 4}
s2 := []int{12, 13, 14}
copy(s1, s2)
fmt.Println(s1) // [12 13 14 4]
fmt.Println(s2) // [12 13 14]
s3 := []int{1, 2, 3, 4}
s4 := []int{12, 13, 14}
copy(s4, s3)
fmt.Println(s3) // [1 2 3 4]
fmt.Println(s4) // [1 2 3]
s5 := []int{1, 2, 3, 4}
s6 := make([]int, 4)
copy(s5, s6)
fmt.Println(s5) // [0 0 0 0]
fmt.Println(s6) // [0 0 0 0]
s7 := []int{1, 2, 3, 4}
s8 := make([]int, 4)
copy(s8, s7)
fmt.Println(s7) // [1 2 3 4]
fmt.Println(s8) // [1 2 3 4]
s8[0] = 100
fmt.Println(s7) // [1 2 3 4]
fmt.Println(s8) // [100 2 3 4]
}
删除
Go 并没有提供删除切片元素的方法,我们可以利用其本身的特性来删除元素
切片可以取自切片,那我们就将被删除元素之前的元素切下来,再把被删除元素之后的元素切下来,然后用append()
拼接
func main() {
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 删除下标为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) // [30, 31, 33, 34, 35, 36, 37]
}
简单说就是 a = append(a[:index], a[index+1:]...)
总结
切片和数组之间有那么点像数据库中的 基本表和视图。
基本表是存储数据定义和数据的,视图只存储数据定义。所以基本表改变时视图也改变。
数组是存储定义和数据的,切片只存储定义。所以数组元素改变时切片也改变。
这个例子可能不是很贴切,但联系一下这句话:数组是值类型,切片是引用类型。
也就是说,数组是实实在在存数据的地方,切片只是对某个数组的引用,自己并没有数据。