因为数组的长度在定义时就固定了,不能灵活调整,所以引入了切片。切片是一个拥有相同类型的长度可变的序列,可以看成是一类不定长的数组。

切片的定义格式
1
2
3
4
var name []T
//name表示切片名,T表示切片类型,比如int
var slice1 = []int{}
//声明一个空切片,默认值是nil,引用类型的默认值都是nil,相当于C语言中的NULL,切片因为底层是数组,所以切片未初始化时是可以使用的,而map就不能直接使用
从连续内存区域生成切片的格式
1
slice [开始位置 : 结束位置)
  • slice:表示目标切片对象;
  • 开始位置:对应目标切片对象的索引,闭区间;
  • 结束位置:对应目标切片的结束索引,开区间,不包含结束位置元素,结束位置的取值上限是切片的容量而不是长度。
切片特性
  • 取出的元素数量为:结束位置 - 开始位置;
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用slice[len(slice)]获取;
  • 当缺省开始位置时,表示从连续区域开头到结束位置;
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
  • 两者同时缺省时,与切片本身等效;
  • 两者同时为 0 时,等效于空切片,一般用于切片复位。
  • 切片之间不能直接进行比较,唯一合法的操作是与nil进行比较,一个nil值的切片没有底层数组,其长度和容量都是0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func main() {
//定义数组,自动初始化值为0
//var array1 [5]int
//fmt.Println(array1)
//定义切片,未初始化的切片为空
var slice1 []int
var slice2 []string
fmt.Println(slice1, slice2)
//使用len获取切片的长度
fmt.Println(len(slice1), len(slice2))
//使用cap获取切片的容量
fmt.Println(cap(slice1))
//基于数组获取切片
var array2 = [5]int{1, 2, 3, 4, 5}
fmt.Println(array2, "array1的容量是", cap(array2))
//将数组array2索引为0~3且不包含第4个元素赋值给切片slice1
slice1 = array2[:3]
//slice1此时的长度是已获得的元素长度为3,容量是数组array2赋给slice1的容量为5,如果对切片赋值时不是从第1个索引0而是从第n索引开始,那么切片的容量就是数组容量-n。
fmt.Println(slice1, "slice1的长度是", len(slice1), "\nslice1的容量是", cap(slice1))
//由于切片是引用类型,如果修改切片的值则会影响到原数组的值
slice1[1] = 99
fmt.Println(array2)
}
完整切片表达式

对于数组,指向数组的指针或者切片(非字符串)支持完整切片表达式

1
2
3
4
5
6
7
a[low : high : max]
//在完整切片表达式中只有第一个索引值(low)可以省略;它默认为0。

//完整切片表达式
slice2 = array2[:3:5]
fmt.Println(slice2)
//完整切片表达式需要满足的条件是`0 <= low <= high <= max <= cap(a)`,其他条件和简单切片表达式相同。

使用make()函数构造切片

make()函数格式:

1
make( []Type, size, cap )
  • Type 是指切片的元素类型
  • size 指的是为这个类型分配多少个元素
  • cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。如果省略,则其默认值为size的值
1
2
3
4
5
6
7
8
slice3 := make([]int, 2)
slice4 := make([]int, 2, 10)
fmt.Println("slice3 len is", len(slice3), "slice3 cap is", cap(slice3))
fmt.Println("slice4 len is", len(slice4), "slice4 cap is", cap(slice4))

//输出结果
slice3 len is 2 slice3 cap is 2
slice4 len is 2 slice4 cap is 10
判断切片是否为空

判断切片是否为空是应该判断切片的长度而不是和nil比较

1
2
3
4
//判断切片是否为空
if len(slice3) == 0 {
fmt.Println("slice5 is null")
}

使用make函数构造的切片实际上在定义size的时候就已经分配了内存而且元素的值被自动初始化为默认值,所以使用make函数构造的切片不为空。

向切片中添加元素

使用append()方法

1
2
3
4
5
6
7
//使用append()方法向切片中添加元素
slice3 = append(slice3, 1, 2, 3)
fmt.Println(slice3, "slice3 cap is", cap(slice3))

//使用append()向切片中添加另一个切片
slice4 = append(slice4, slice2...)
fmt.Println(slice4, "slice4 cap is", cap(slice4))

向切片追加元素时,切片会自动扩容,扩容策略是2*原切片容量或者2*追加元素个数,计算方法的第二个值以较大的为准。

切片的值引用

切片是引用类型,两个切片指向同一块内存地址时,修改一个切片会影响到另一个切片,使用copy()函数对切片进行值引用。它可以将原切片的数据完全复制给目标切片。格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
copy(destSlice, srcSlice []T)

//使用copy()函数对切片值引用
fmt.Println("slice1=", slice1)
slice5 := make([]int, 5)
//将slice1的值赋值给slice5
copy(slice5, slice1)
fmt.Println("slice5=", slice5)
//修改slice5中的元素不会影响到slice1
slice5[2] = 23
fmt.Println("slice1=", slice1, "slice5=", slice5)

//输出结果
slice1= [1 99 3]
slice5= [1 99 3 0 0]
slice1= [1 99 3] slice5= [1 99 23 0 0]

//如果原函数的长度大于目标函数,copy()函数就只会将前几个元素赋值给目标切片

从切片中删除元素

利用切片的特性删除切片中的元素

1
2
3
4
5
6
7
8
func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
//a[:2]表示0~1索引的元素,a[3:]表示3~最后一位元素,append表示在0~1号索引元素后追加3~最后一位索引的元素,这中间就剔除了索引为2的元素
fmt.Println(a) //[30 31 33 34 35 36 37]
}