复杂数据类型-切片slice
# 1. 切片的引入
切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ (opens new window)
中的数组类型,或者Python (opens new window)中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。
Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作,格式如下:
slice [开始位置 : 结束位置]
语法说明如下:
- slice:表示目标切片对象;
- 开始位置:对应目标切片对象的索引;
- 结束位置:对应目标切片的结束索引。
slice由三个部分构成,指针、长度、容量
指针:指针指向slice第一个元素
对应的数组元素
的地址。
长度:slice元素的数量,不得超过容量。
容器:slice开始的位置
到底层数据的结尾
。
package main
import "fmt"
func main() {
var arr [6]int = [6]int{6, 5, 4, 3, 2, 1}
fmt.Println("原数组", arr)
// 切片构建在数组之上
intArr := arr[2:6] // 前包后不包
fmt.Println("输出切片", intArr)
fmt.Println("输出切片个数", len(intArr))
fmt.Println("输出切片容量(容量可以动态变化)", cap(intArr))
}
/*
原数组 [6 5 4 3 2 1]
输出切片 [4 3 2 1]
输出切片个数 4
输出切片容量(容量可以动态变化) 4
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2. 内存分析
slice由三个部分构成,指针、长度、容量
- 指针:指针指向slice
第一个元素
对应的数组元素
的地址。 - 长度:slice元素的数量,不得超过容量。
- 容器:slice
开始的位置
到底层数据的结尾
。
切片有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。
切片本质还是保存的原数组的地址,所以修改了切片的内容数组的值也会被修改,相反修改了数组的内容切片也会被修改(切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充)
package main
import "fmt"
func main() {
var arr [5]int = [5]int{3, 6, 1, 4, 7}
fmt.Println("原数组", arr)
// 切片构建在数组之上
intArr := arr[1:4] // 前包后不包
fmt.Println("输出切片", intArr)
fmt.Println("输出切片个数", len(intArr))
fmt.Println("输出切片容量(容量可以动态变化)", cap(intArr))
fmt.Println("数组下标为1的地址", &arr[1])
fmt.Println("切片下标为1的地址", &intArr[1]) // 切片本质还是保存的原数组的地址,所以修改了切片的内容数组的值也会被修改
intArr[1] = 16
arr[1] = 99
fmt.Println(arr)
fmt.Println(intArr)
}
/*
原数组 [3 6 1 4 7]
输出切片 [6 1 4]
输出切片个数 3
输出切片容量(容量可以动态变化) 4
数组下标为1的地址 0xc00000c368
切片下标为1的地址 0xc00000c370
[3 99 16 4 7]
[99 16 4]
*/
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
28
29
30
# 3. 切片的定义
# 1. 从数组或切片生成新的切片
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
从连续内存区域生成切片是常见的操作,格式如下:
slice [开始位置 : 结束位置]
语法说明如下:
- slice:表示目标切片对象;
- 开始位置:对应目标切片对象的索引;
- 结束位置:对应目标切片的结束索引。
从数组或切片生成新的切片拥有如下特性:
- 取出的元素数量为:结束位置 - 开始位置;
- 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
- 当缺省开始位置时,表示从连续区域开头到结束位置;
- 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
- 两者同时缺省时,与切片本身等效;
- 两者同时为 0 时,等效于空切片,一般用于切片复位。
根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误,生成切片时,结束位置可以填写 len(slice) 但不会报错。
# 从指定范围中生成切片
var highRiseBuilding [30]int
for i := 0; i < 30; i++ {
highRiseBuilding[i] = i + 1
}
// 区间
fmt.Println(highRiseBuilding[10:15])
// 中间到尾部的所有元素
fmt.Println(highRiseBuilding[20:])
// 开头到中间指定位置的所有元素
fmt.Println(highRiseBuilding[:2])
[11 12 13 14 15]
[21 22 23 24 25 26 27 28 29 30]
[1 2]
2
3
4
5
6
7
8
9
10
11
12
13
14
# 表示原有的切片
a := []int{1, 2, 3}
fmt.Println(a[:])
[1 2 3]
a 是一个拥有 3 个元素的切片,将 a 切片使用 a[:] 进行操作后,得到的切片与 a 切片一致,代码输出如下:
2
3
4
5
# 重置切片,清空拥有的元素
把切片的开始和结束位置都设为 0 时,生成的切片将变空,代码如下
a := []int{1, 2, 3}
fmt.Println(a[0:0])
[]
2
3
4
# 2. 直接声明新的切片
除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明
var name []Type
其中 name 表示切片的变量名,Type 表示切片对应的元素类型。
// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty)
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3. 使用 make() 函数构造切片
如果需要动态地创建一个切片,可以使用 make() 内建函数
make( []Type, size, cap )
Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
[0 0] [0 0]
2 2
2
3
4
5
6
7
其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素。
容量不会影响当前的元素个数,因此 a 和 b 取 len 都是 2。
# 注意点
使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。
# 代码示例
package main
import "fmt"
func main() {
// 定义数组
var arrAry [30]int
for i := 0; i < 30; i++ {
arrAry[i] = i + 1
}
// 一、从数组或切片生成新的切片
// 1. 从指定范围中生成切片
// 区间
fmt.Println(arrAry[10:15])
// 中间到尾部的所有元素
fmt.Println(arrAry[20:])
// 开头到中间指定位置的所有元素
fmt.Println(arrAry[:2])
// 2. 表示原有的切片
fmt.Println(arrAry[:])
// 3. 重置切片,清空拥有的元素
fmt.Println(arrAry[0:0])
// 二、直接声明新的切片
// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
// 输出3个切片
fmt.Println(strList, numList, numListEmpty)
// 输出3个切片大小
fmt.Println(len(strList), len(numList), len(numListEmpty))
// 切片判定空的结果
fmt.Println(strList == nil)
fmt.Println(numList == nil)
fmt.Println(numListEmpty == nil)
// 三、使用 make() 函数构造切片 make( []Type, size, cap )
// Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题**
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
// 其中 a 和 b 均是预分配 2 个元素的切片,只是 b 的内部存储空间已经分配了 10 个,但实际使用了 2 个元素
}
/*
[11 12 13 14 15]
[21 22 23 24 25 26 27 28 29 30]
[1 2]
[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 28 29 30]
[]
[] [] []
0 0 0
true
true
false
[0 0] [0 0]
2 2
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 4. 切片的增删改查
Go语言append()为切片添加元素 (opens new window)
# 增
函数 append() 可以为切片动态添加元素,在使用 append() 函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行“扩容”,此时新切片的长度会发生改变。切片在扩容时,容量的扩展规律是按容量的 2 倍数进行扩充
package main
import "fmt"
func main() {
// 定义切片
var slice = make([]int, 5, 10)
fmt.Println("原切片", slice)
// 增
slice = append(slice, 1) // 追加1个元素
fmt.Println(slice)
slice = append(slice, 1, 2, 3) // 追加多个元素, 手写解包方式
fmt.Println(slice)
slice = append(slice, []int{1, 2, 3}...) // 追加一个切片, 切片需要解包
fmt.Println(slice)
slice = append([]int{9}, slice...) // 在开头添加1个元素
fmt.Println(slice)
slice = append([]int{-3, -2, -1}, slice...) // 在开头添加1个切片
fmt.Println(slice)
slice = append(slice[:2], append([]int{99}, slice[1:]...)...) // 在第i个位置插入x 切片也支持链式操作
fmt.Println(slice)
slice = append(slice[:2], append([]int{99, 88, 77}, slice[1:]...)...) // 在第i个位置插入切片
fmt.Println(slice)
}
/*
原切片 [0 0 0 0 0]
[0 0 0 0 0 1]
[0 0 0 0 0 1 1 2 3]
[0 0 0 0 0 1 1 2 3 1 2 3]
[9 0 0 0 0 0 1 1 2 3 1 2 3]
[-3 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[-3 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[-3 -2 99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
*/
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
28
29
30
31
32
33
34
35
36
# 删
Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。
Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来。
)
package main
import "fmt"
func main() {
// 定义切片
var slice = [...]int{-3 -2 99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3}
fmt.Println("--------------------------------")
// 删
// 删除开头元素
// 删除开头的元素可以直接移动数据指针
slice = slice[1:] // 删除开头1个元素:
fmt.Println(slice)
// 删除开头1个元素:也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)
slice = append(slice[:0], slice[1:]...)
fmt.Println(slice)
// 用 copy() 函数来删除开头的元素
slice = slice[:copy(slice, slice[1:])] // 删除开头1个元素
fmt.Println(slice)
// 删除中间元素
// 对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成
slice = append(slice[:2], slice[4:]...) // 删除下标2到3的元素 slice = append(slice[:i], slice[i+N:]...) // 删除中间N个元素
fmt.Println(slice)
slice = slice[:2+copy(slice[2:], slice[3:])] // 删除中间1个元素 slice = slice[:i+copy(slice[i:], slice[i+N:])] // 删除中间N个元素
fmt.Println(slice)
// 从尾部删除
slice = slice[:len(slice)-1] // 删除尾部1个元素
//slice = slice[:len(slice)-N] // 删除尾部N个元素
fmt.Println(slice)
}
/*
[-3 -2 99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
--------------------------------
[-2 99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -1 9 0 0 0 0 0 1 1 2 3 1 2]
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 改
slice[0] = 9999
fmt.Println(slice)
[88 77 -1 9 0 0 0 0 0 1 1 2 3 1 2]
[9999 77 -1 9 0 0 0 0 0 1 1 2 3 1 2]
2
3
4
5
# 查
# 遍历
//方式1:普通for循环
for i := 0; i < len(slice); i++ {
fmt.Printf("slice[%v] = %v \\t", i, slice[i])
}
fmt.Println("\\n------------------------------")
//方式2:for-range循环:
for i, v := range slice {
fmt.Printf("下标:%v ,元素:%v\\n", i, v)
}
2
3
4
5
6
7
8
9
# 代码示例
package main
import "fmt"
func main() {
// 定义切片
var slice = make([]int, 5, 10)
fmt.Println("原切片", slice)
// 增
slice = append(slice, 1) // 追加1个元素
fmt.Println(slice)
slice = append(slice, 1, 2, 3) // 追加多个元素, 手写解包方式
fmt.Println(slice)
slice = append(slice, []int{1, 2, 3}...) // 追加一个切片, 切片需要解包
fmt.Println(slice)
slice = append([]int{9}, slice...) // 在开头添加1个元素
fmt.Println(slice)
slice = append([]int{-3, -2, -1}, slice...) // 在开头添加1个切片
fmt.Println(slice)
slice = append(slice[:2], append([]int{99}, slice[1:]...)...) // 在第i个位置插入x 切片也支持链式操作
fmt.Println(slice)
slice = append(slice[:2], append([]int{99, 88, 77}, slice[1:]...)...) // 在第i个位置插入切片
fmt.Println(slice)
fmt.Println("--------------------------------")
// 删
// 删除开头元素
// 删除开头的元素可以直接移动数据指针
slice = slice[1:] // 删除开头1个元素:
fmt.Println(slice)
// 删除开头1个元素:也可以不移动数据指针,但是将后面的数据向开头移动,可以用 append 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化)
slice = append(slice[:0], slice[1:]...)
fmt.Println(slice)
// 用 copy() 函数来删除开头的元素
slice = slice[:copy(slice, slice[1:])] // 删除开头1个元素
fmt.Println(slice)
// 删除中间元素
// 对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 append 或 copy 原地完成
slice = append(slice[:2], slice[4:]...) // 删除下标2到3的元素 slice = append(slice[:i], slice[i+N:]...) // 删除中间N个元素
fmt.Println(slice)
slice = slice[:2+copy(slice[2:], slice[3:])] // 删除中间1个元素 slice = slice[:i+copy(slice[i:], slice[i+N:])] // 删除中间N个元素
fmt.Println(slice)
// 从尾部删除
slice = slice[:len(slice)-1] // 删除尾部1个元素
//slice = slice[:len(slice)-N] // 删除尾部N个元素
fmt.Println(slice)
// 改
slice[0] = 9999
fmt.Println(slice)
// 查
//方式1:普通for循环
for i := 0; i < len(slice); i++ {
fmt.Printf("slice[%v] = %v \t", i, slice[i])
}
fmt.Println("\n------------------------------")
//方式2:for-range循环:
for i, v := range slice {
fmt.Printf("下标:%v ,元素:%v\n", i, v)
}
}
/*
原切片 [0 0 0 0 0]
[0 0 0 0 0 1]
[0 0 0 0 0 1 1 2 3]
[0 0 0 0 0 1 1 2 3 1 2 3]
[9 0 0 0 0 0 1 1 2 3 1 2 3]
[-3 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[-3 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[-3 -2 99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
--------------------------------
[-2 99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[99 88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -2 99 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -2 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -1 9 0 0 0 0 0 1 1 2 3 1 2 3]
[88 77 -1 9 0 0 0 0 0 1 1 2 3 1 2]
[9999 77 -1 9 0 0 0 0 0 1 1 2 3 1 2]
slice[0] = 9999 slice[1] = 77 slice[2] = -1 slice[3] = 9 slice[4] = 0 slice[5] = 0 slice[6] = 0 slice[7] = 0 slice[8] = 0 slice[9] = 1 slice[10] = 1 slice[11] = 2 slice[12] = 3 slice[13] = 1 slice[14] = 2
------------------------------
下标:0 ,元素:9999
下标:1 ,元素:77
下标:2 ,元素:-1
下标:3 ,元素:9
下标:4 ,元素:0
下标:5 ,元素:0
下标:6 ,元素:0
下标:7 ,元素:0
下标:8 ,元素:0
下标:9 ,元素:1
下标:10 ,元素:1
下标:11 ,元素:2
下标:12 ,元素:3
下标:13 ,元素:1
下标:14 ,元素:2
*/
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# 5. copy() 切片复制(切片拷贝)
Go语言copy():切片复制(切片拷贝) (opens new window)
# 介绍
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。
copy() 函数的使用格式如下:
copy( destSlice, srcSlice []T) int
其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{51, 41, 31}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
fmt.Println(slice1, slice2)
slice3 := []int{1, 2, 3, 4, 5}
slice4 := []int{51, 41, 31}
copy(slice3, slice4) // 只会复制slice4的3个元素到slice3的前3个位置
fmt.Println(slice3, slice4)
}
/*
[1 2 3 4 5] [1 2 3]
[51 41 31 4 5] [51 41 31]
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 6. 多维切片
# 7. 切片注意点
# 1. 切片定义后不可以直接使用,需要让其引用到一个数组,或者make一个空间供切片来使用
# 2. 切片使用不能越界
# 3. 切片可以继续切片
# 4. 简写方式
# 5. 切片可以动态增长
package main
import "fmt"
func main(){
//定义数组:
var intarr [6]int = [6]int{1,4,7,3,6,9}
//定义切片:
var slice []int = intarr[1:4] //4,7,3
fmt.Println(len(slice))
slice2 := append(slice,88,50)
fmt.Println(slice2) //[4 7 3 88 50]
fmt.Println(slice)
//底层原理:
//1.底层追加元素的时候对数组进行扩容,老数组扩容为新数组:
//2.创建一个新数组,将老数组中的4,7,3复制到新数组中,在新数组中追加88,50
//3.slice2 底层数组的指向 指向的是新数组
//4.往往我们在使用追加的时候其实想要做的效果给slice追加:
slice = append(slice,88,50)
fmt.Println(slice)
//5.底层的新数组 不能直接维护,需要通过切片间接维护操作。
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20