切片(slice)
文章发布较早,内容可能过时,阅读注意甄别。
# 1,概述。
数组的长度在定义之后无法再次修改,数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法满足开发者的真实需求。Go 语言提供了数组切片(slice)来弥补数组的不足。
切片并不是数组或数组指针,它通过内部指针和相关属性引用数组片段,以实现边长方案。
slice 并不是真正意义上的动态数组,而是一个引用类型。slice 总是指向一个底层 array,slice 的声明也可以像 array 一样,只是不需要长度。
# 2,认识切片。
a := [...]int{1,2,3,4,5}
s := a[0:3:5]
定义一个数组a,然后通过s对其进行切片。
a[0:3:5]表示:a[low:high:max]
low:表示下标的起点,如果是0,则从第一个开始。
high:表示下标的终点,(不包括此下标),那么实际终点应该是high-1
得有两个概念:长度和容量。
长度表示切片的长度=high-low
容量表示切片的容量=max-low
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
用代码举例:
package main
import "fmt"
func main() {
a := [5]int{1, 2, 3, 4, 5}
s := a[0:3:5]
fmt.Println("s = ", s) //切片的内容
fmt.Println("len(s) =", len(s)) //切片的长度=3-0
fmt.Println("cap(s) =", cap(s)) //切片的容量=5-0
s = a[1:4:5]
fmt.Println("s = ", s) //从下标1开始,取4-1=3个
fmt.Println("len(s) =", len(s)) //切片的长度=4-1
fmt.Println("cap(s) =", cap(s)) //切片的容量=5-1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3,数组和切片的区别。
package main
import "fmt"
func main() {
//切片和数组的区别
//数组[]里面的长度是一个固定的常量,数组不能修改长度,len和cap永远都是5
a := [5]int{}
fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))
//切片的[]里面为空,或者为...
//切片的长度或容量不固定
s := []int{}
fmt.Printf("1:len = %d, cap = %d\n", len(s), cap(s)) //如果没定义,则默认为0
s = append(s, 3) //append表示给切片末尾追加一个元素
fmt.Printf("2:len = %d, cap = %d\n", len(s), cap(s))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4,切片的初始化。
package main
import "fmt"
func main() {
//自动推导类型,同时初始化
s := []int{1, 2, 3, 4}
fmt.Println("s = ", s)
//借助make函数,格式为: make(切片类型,长度,容量)
s1 := make([]int, 5, 10)
fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1))
//其中容量可以省略,没写的话,容量等于长度
s2 := make([]int, 5)
fmt.Printf("len = %d, cap = %d\n", len(s2), cap(s2))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5,切片截取。
操作 | 含义 |
---|---|
s[n] | 切片 s 中下标为 n 的项 |
s[:] | 从切片 s 的下标为 0 到 len(s)-1 处所获得的切片内容 |
s[low:] | 从切片 s 的下标为 low 到 len(s)-1 处所获得的切片 |
s[:high] | 从切片 s 的下标为 0 到 high 处所获得的切片 |
s[low:high] | 从切片 s 的下标为 low 到 high 处所获得的切片 |
s[low:high:max] | 从切片 s 的下标为 low 到 high 处所获得的切片 |
len(s) | 切片 s 的长度,总是<=cap(s) |
cap(s) | 切片 s 的容量,总是>=len(s) |
示例说明:
有如下切片类型:
a := []int{0,1,2,3,4,5,6,7,8,9}
1
那么:
操作 | 结果 | len | map | 说明 |
---|---|---|---|---|
a[:] | [0 1 2 3 4 5 6 7 8 9] | 10 | 10 | 等价于[0:len(a):cap(a)] |
a[3] | [2] | 无 | 无 | 无 |
a[3:6:7] | [3 4 5] | 3 | 4 | 如见 |
a[:6] | [0 1 2 3 4 5] | 6 | 10 | 等价于[0:6:cap(a)] 常用 |
a[5:] | [5 6 7 8 9] | 5 | 5 | 等价于[5:len(a):cap(a)] |
代码示例如下:
package main
import "fmt"
func main() {
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := a[:] //等价于[0:len(a):cap(a)]
fmt.Println("s1=", s1)
fmt.Printf("len=%d, cap=%d\n", len(s1), cap(s1))
//操作切片中的某个元素,方法和数组的一样
s2 := a[5]
fmt.Println("s2=", s2)
s3 := a[3:6:7] //[a[3], a[4], a[5]] len=6-3 cap=7-3
fmt.Println("s3=", s3)
fmt.Printf("len=%d, cap=%d\n", len(s3), cap(s3))
s4 := a[:6] //等价于[0:6:cap(a)] len=6-0 cap=10-0
fmt.Println("s4=", s4)
fmt.Printf("len=%d, cap=%d\n", len(s4), cap(s4))
s5 := a[3:] //等价于[3:len(a):cap(a)]
fmt.Println("s5=", s5)
fmt.Printf("len=%d, cap=%d\n", len(s5), cap(s5))
}
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
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
# 6,切片和底层数组的关系。
package main
import "fmt"
func main() {
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
//新切片
s1 := a[2:5]
s1[1] = 666
fmt.Println("s1 =", s1)
fmt.Println("a =", a)
//另一新切片
s2 := s1[1:7]
fmt.Println("s2 =", s2)
s2[2] = 777
fmt.Println("a =", a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上边程序输出结果为:
$ go run 19_切片和底层数组的关系.go
s1 = [2 666 4]
a = [0 1 2 666 4 5 6 7 8 9]
s2 = [666 4 5 6 7 8]
a = [0 1 2 666 4 777 6 7 8 9]
1
2
3
4
5
2
3
4
5
第一个切片 s1 的结果还是比较容易理解的,但是到了第二个切片 s2 这里,有稍微有点让人感到费解了,而这,正式这一小节标题的意义,切片与底层数组的关系。
可画示意图帮助理解:
# 7,内建函数。
# 1,append。
# 1,append 简单使用。
append 函数用于向 slice 尾部追加元素。
package main
import "fmt"
func main() {
a := []int{}
fmt.Printf("len = %d, cap = %d, a =%d\n", len(a), cap(a), a)
//在原切片的末尾添加元素
a = append(a, 1)
a = append(a, 2, 3)
fmt.Printf("len = %d, cap = %d, a =%d\n", len(a), cap(a), a)
b := make([]int, 5)
fmt.Printf("len = %d, cap = %d, b =%d\n", len(b), cap(b), b)
b = append(b, 6)
fmt.Printf("len = %d, cap = %d, b =%d\n", len(b), cap(b), b)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2,append 扩容特点。
append 函数会智能地控制底层数组的容量增长,一旦超过原底层数组容量,通常以原容量 2 倍的数值定义给新容量,并复制原来的数据。
package main
import "fmt"
func main() {
//如果超过原来的容量,通常以2倍容量扩容
s := make([]int, 0, 1)
oldCap := cap(s)
for i := 0; i < 10; i++ {
s = append(s, i)
if newCap := cap(s); oldCap < newCap {
fmt.Printf("cap: %d===> %d\n", oldCap, newCap)
oldCap = newCap
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
大概有如下特征:
- 首先判断,如果新申请的容量(cap)大于 2 倍的旧容量(old.cap),那么最终容量(newcap)就是新申请容量的容量(cap)。
- 否则判断,如果旧切片的长度小于 1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)。
- 否则判断,如果旧切片长度大于等于 1024,则最终容量(newcap)从旧容量(old.cap)的 1/4 循环增加,即(newcap=old.cap,for {newcap+=newcap/4}),直到最终容量大于等于新申请的容量。
- 如果最终容量计算溢出,则最终容量就是新申请容量(cap)。
需要注意的是,切片扩容还会根据切片中元素的类型不同而作不同的处理,比如
int
和string
类型的处理方式就不一样。
package main
import "fmt"
func main() {
s1 := []string{"aaa", "bbb", "ccc"}
fmt.Println(s1)
fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
s1 = append(s1, "ddd")
fmt.Println(s1)
fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
s2 := []string{"aa", "bb", "cc"}
s1 = append(s1, s2...) //...表示展开s2这个数组/切片
fmt.Println(s1)
fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2,copy。
package main
import "fmt"
func main() {
src := []int{1, 2}
dst := []int{6, 6, 6, 6, 6, 6}
copy(dst, src) //表示将src的内容copy给dst,然后替换1,2对应位置的元素
fmt.Println("dst =", dst)
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准,两个 slice 可指向同一底层数组。
package main
import "fmt"
func main() {
a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
b := a[8:]
c := a[:5]
fmt.Printf("a = %d,b = %d,c = %d\n", a, b, c)
copy(c, b)
fmt.Println("c = ", c)
fmt.Println("a = ", a)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8,切片作为函数参数。
package main
import (
"fmt"
"math/rand"
"time"
)
//生成几个随机数
func InitData(s []int) {
//设置种子
rand.Seed(time.Now().UnixNano())
for i := 0; i < len(s); i++ {
s[i] = rand.Intn(100) //100以内的10个随机数
}
}
//冒泡排序
func BubbleSort(s []int) {
for i := 0; i < len(s); i++ {
for j := 0; j < len(s)-1-i; j++ {
if s[j] > s[j+1] {
s[j], s[j+1] = s[j+1], s[j]
}
}
}
}
func main() {
n := 10
//创建一个切片,len为n
s := make([]int, n)
fmt.Println("s =", s)
InitData(s)
fmt.Println("排序前: ", s)
BubbleSort(s)
fmt.Println("排序后: ", s)
}
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
31
32
33
34
35
36
37
38
39
40
41
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
切片做函数参数的时候,是引用传递,当函数对切片进行操作之后,切片的内容也会随之改变。
# 9,写一个猜数字游戏。
# 1,生成一个 4 位的随机数。
package main
import (
"fmt"
"math/rand"
"time"
)
//生成几个随机数
func CreateNum(p *int) {
//设置种子
rand.Seed(time.Now().UnixNano())
var num int
for {
num = rand.Intn(10000)
if num >= 1000 {
break
}
}
*p = num
}
func main() {
var randNum int
//产生一个4位随机数
CreateNum(&randNum)
fmt.Println("randNum: ", randNum)
}
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
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
# 2,取出每一个数。
package main
import (
"fmt"
"math/rand"
"time"
)
//生成几个随机数
func CreateNum(p *int) {
//设置种子
rand.Seed(time.Now().UnixNano())
var num int
for {
num = rand.Intn(10000)
if num >= 1000 {
break
}
}
*p = num
}
//取出每一位
func GetNum(s []int, num int) {
s[0] = num / 1000 //取千位
s[1] = num % 1000 / 100 //取百位
s[2] = num % 100 / 10 //取十位
s[3] = num % 10
}
func main() {
var randNum int
//产生一个4位随机数
CreateNum(&randNum)
fmt.Println("randNum: ", randNum)
randSlice := make([]int, 4)
//保存着个4位数的每一位
GetNum(randSlice, randNum)
fmt.Println("randSlice: ", randSlice)
}
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
31
32
33
34
35
36
37
38
39
40
41
42
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
# 3,最终成型的样子。
package main
import (
"fmt"
"math/rand"
"time"
)
//生成几个随机数
func CreateNum(p *int) {
//设置种子
rand.Seed(time.Now().UnixNano())
var num int
for {
num = rand.Intn(10000)
if num >= 1000 {
break
}
}
*p = num
}
//取出每一位
func GetNum(s []int, num int) {
s[0] = num / 1000 //取千位
s[1] = num % 1000 / 100 //取百位
s[2] = num % 100 / 10 //取十位
s[3] = num % 10
}
func OnGame(randSlice []int) {
var num int
keySlice := make([]int, 4)
for {
for {
fmt.Printf("请输入一个4位数:")
fmt.Scan(&num)
//限定输入的范围 999 < num < 10000
if 999 < num && num < 10000 {
break
}
//fmt.Println("输入的数字不符合规范。")
}
//fmt.Println("num = ", num)
GetNum(keySlice, num)
//fmt.Println("keySlice = ", keySlice)
n := 0
for i := 0; i < 4; i++ {
if keySlice[i] > randSlice[i] {
fmt.Printf("第%d位的数字大了\n", i+1)
} else if keySlice[i] < randSlice[i] {
fmt.Printf("第%d位的数字小了\n", i+1)
} else {
fmt.Printf("第%d位的数字猜对了\n", i+1)
n++
}
}
if n == 4 {
fmt.Println("全部都猜对了!!")
break
}
}
}
func main() {
var randNum int
//产生一个4位随机数
CreateNum(&randNum)
//fmt.Println("randNum: ", randNum)
//取出这个四位数的每一位
randSlice := make([]int, 4)
GetNum(randSlice, randNum)
//fmt.Println("randSlice: ", randSlice)
//输入一个四位数
OnGame(randSlice)
}
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
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
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
上次更新: 2024/11/19, 23:11:42