Go切片

语法定义

切片(slice)是对数组的一个连续片段的引用。切片属于引用类型。

切片三个要素

1.起始位置:切片引用数组的开始位置。

2.大小:切片中的元素个数。切片中的大小不能超过容量数量。可以使用len()函数对切片统计大小。

3.容量:切片最大可存的元素个数。如果空间不足以容纳足够多的元素,切片就会进行动态“扩容”,此时新切片的长度会发生改变。一般切片的扩容是按照扩容前容量的2倍。可以使用cap()函数对切片容量进行统计。

切片与数组的区别

1.切片是对数组中的连续引用。切片的初始位置指向数组的内存地址,如果切片的值改变,数组对应的值也会对应改变。

2.切片的长度是动态的,本质上是一个可变的动态数组。数组的长度在定义的时候就决定好了,后期是无法修改数组的长度的。

3.切片的长度是可以动态扩容的[如上面容量一次提到的]。

切片内存分布

go-slice-1

切片的定义

通过数组定义

1
array[startIndex:endIndex]

1.array:被切片定义的数组名称。

2.startIndex:切片的开始位置。

3.endIndex:切片的结束位置。结束位置不包含在内的。

1
2
3
array := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "G", "K", "L"}

slice := array[2:5]

从数组或切片生成新的切片拥有如下特性:
1.取出的元素数量为:结束位置 - 开始位置。

2.取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取。

3.当缺省开始位置时,表示从连续区域开头到结束位置。

4.当缺省结束位置时,表示从开始位置到整个连续区域末尾。

5.两者同时缺省时,与切片本身等效。

6.两者同时为0时,等效于空切片,一般用于切片复位。

直接创建切片

1
var name []type

1.name:切片名称。

2.type:切片类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 直接申明切片
var sliceName1 []string // 直接申明一个空切片

var sliceName2 = []int{} // 申明一个值为空的切片

var sliceName3 = []int{1, 2, 3} // 申明一个切片,并初始化值

if sliceName1 == nil {
fmt.Println("sliceName1是空切片")
}

if sliceName2 == nil {
fmt.Println("sliceName2是空切片")
}

if sliceName3 == nil {
fmt.Println("sliceName3是空切片")
}
fmt.Println(sliceName1, sliceName2, sliceName3)
1
2
3
// 输出内容
sliceName1是空切片
[] [] [1 2 3]

因为sliceName2在定义时,给初始化了一个空值。虽然该切片的内容是空,但实际是分配过内存了。如下演示代码:

1
2
3
// 测试三个指针的内存地址
fmt.Printf("%p\t%p\t%p", sliceName1, sliceName2, sliceName3)
0x0 0x1195a98 0xc00008a020

使用make创建指针

1
make([]type,size,cap)

1.type 是指切片的元素类型。

2.size 指的是为这个类型分配多少个元素。

3.cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。

使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

1
2
3
4
5
6
7
8
9
10
// 使用make方式创建切片
slice1 := make([]string, 2, 3)
slice1[0] = "1"
fmt.Println(slice1)

slice2 := make([]string, 2, 3)
fmt.Println(slice2)

fmt.Println("切片的长度为", len(slice1))
fmt.Println("切片的容量为", cap(slice1))
1
2
3
4
5
// 输出结果
[1 ]
[ ]
切片的长度为 2
切片的容量为 3

当切片未给对应的下标赋值时,会根据定义的切片类型初始化一个值。

切片的迭代

切片从本质上来讲,是一个动态的数组,因此可以直接使用for循环进行迭代即可。

1
2
3
4
5
// 使用for迭代切片
slice := []int{1, 2, 3, 4}
for index, value := range slice {
fmt.Println("切片slice的index,value分别为", index, value)
}
1
2
3
4
5
// 输出结果
切片slice的index,value分别为 0 1
切片slice的index,value分别为 1 2
切片slice的index,value分别为 2 3
切片slice的index,value分别为 3 4

切片与数组的区别

1
2
3
4
5
6
// 通过数组定义切片
var array1 = [3]int{1, 1, 3}
fmt.Println("数组的元素分别是:", array1)
slice1 := array1[0:2]
slice1[1] = 11111
fmt.Println("数组的元素分别是:", array1)
1
2
3
// 输出结果
数组的元素分别是: [1 1 3]
数组的元素分别是: [1 11111 3]

操作切片

追加切片

1
append(切片,追加元素)

1.尾部追加

1
2
3
4
5
6
7
8
9
10
11
12
// 1.默认是向切片的尾部追加元素
slice8 := make([]int, 2, 3)
fmt.Println("slice8:", slice8)
fmt.Println("slice8的容量是:", cap(slice8))
// 追加多个元素,手动解包
slice8 = append(slice8, 3, 4)
fmt.Println(slice8)
// 这里需要使用...,切片需要进行解包
slice8 = append(slice8, []int{1, 2, 3, 4}...)
fmt.Println(slice8)
// 当切片的追加容量超过初始容量,切片会自动进行扩容,扩容的以2倍增长。
fmt.Println("slice8的容量是:", cap(slice8))
1
2
3
4
5
slice8: [0 0]
slice8的容量是: 3
[0 0 3 4]
[0 0 3 4 1 2 3 4]
slice8的容量是: 12

2.头部添加

1
2
3
// 头部添加
slice8 = append(make([]int, 2, 3), slice8...)
fmt.Println("向头部追加元素", slice8)

在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制1次。因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。

1
2
3
4
5
6
// 测试向头部添加,切片内存地址变化
slice8 := make([]int, 2, 3)
fmt.Printf("切片内存地址:%p", slice8)
slice8 = append(make([]int, 1, 3), slice8...)
fmt.Println("追加切片元素", slice8)
fmt.Printf("切片内存地址:%p", slice8)
1
2
3
4
// 输出结果
切片内存地址:0xc00008a0e0
追加切片元素 [0 0 0]
切片内存地址:0xc00008a100

3.中间添加

a.每个添加操作中的第二个append调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 中。

b.基本公式:append(sliceName[0:x], append(要追加的切片, sliceName[x:]…)…)向x位置追加一个切片。

1
2
3
4
5
6
//向切片的中间位置添加元素
slice9 := make([]int, 3, 10)
fmt.Println("slice9", slice9)
// 在第2个位置追加一个大小为3的切片
slice9 = append(slice9[0:2], append([]int{1, 2, 3}, slice9[2:]...)...)
fmt.Println(slice9)

删除切片

Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。切片的删除,发生在切片内部指针的移动,切片的内存地址是不会发生变化。

1.头部删除

1
2
3
4
5
6
7
8
9
10
11
12
// 切片头部删除
slice1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
fmt.Println("slice1切片的元素分配为:", slice1)
fmt.Printf("%p\n", &slice1)
// a.直接删除
slice1 = slice1[3:len(slice1)]
fmt.Printf("%p\n", &slice1)
// b.使用copy删除
slice1 = slice1[:copy(slice1, slice1[3:])]
// c.使用append删除
slice1 = append(slice1[:0], slice1[3:]...)
fmt.Println("删除后的切片为:", slice1)
1
2
3
4
5
// 输出内容
slice1切片的元素分配为: [1 2 3 4 5 6 7 8 9 10 11]
0xc00000c080
0xc00000c080
删除后的切片为: [4 5 6 7 8 9 10 11]

go-slice-2
2.中间删除

1
2
3
4
5
// 中间删除切片
// a.使用append删除
slice1 = append(slice1[:3], slice1[4:]...)
// b.使用copy删除
slice1 = slice1[:3+copy(slice1[3:], slice1[4:])]
1
2
3
4
// 输出结果
slice1切片的元素分配为: [1 2 3 4 5 6 7 8 9 10 11]
0xc00000c080
删除后的切片为: [1 2 3 5 6 7 8 9 10 11]

go-slice-3

3.尾部删除

1
2
3
slice1切片的元素分配为: [1 2 3 4 5 6 7 8 9 10 11]
0xc00007c020
删除后的切片为: [1 2 3 4 5 6 7 8]
1
2
3
4
// 输出内容为
slice1切片的元素分配为: [1 2 3 4 5 6 7 8 9 10 11]
0xc00007c020
删除后的切片为: [1 2 3 4 5 6 7 8]

go-slice-4

通过发现,这里有一个固定的公式,这里的3可以改为N,即代表删除N个元素。上面的copy和append方式一致。

复制切片

Go切片复制可以使用内置的copy()函数将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。

1
copy(desSlice, srcSlice) int

copy()函数返回的是切片复制的个数。

1
2
3
4
5
6
7
8
// 切片复制
slice10 := make([]int, 5, 10)
slice11 := []int{1, 2, 3, 4}
slice12 := slice10
num := copy(slice11, slice10)
fmt.Println("slice11", slice11)
fmt.Printf("%p,%p,%p", &slice10, &slice11, &slice12)
fmt.Println(num)

切片的引用,被引用的切片发生改变,引用的切片也会发生改变。切片的赋值,原切片发生变动,赋值的切片不会变动。

1
2
3
4
5
6
7
// 打印结果
slice11 [0 0 0 0]
0xc00007c180,0xc00007c1a0,0xc00007c1c04
引用切片的值 999
复制切片的值 0 999
4 5 2 3 4 slice11 [0 0 0 0]
0xc00007c200,0xc00007c220,0xc00007c2404