11.切片(slice)
2023年1月6日约 2588 字大约 9 分钟
11.切片(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
用代码举例:
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
}
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))
}
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))
}
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}
那么:
操作 | 结果 | 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 | 5 | 如见 |
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))
}
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)
}
上边程序输出结果为:
$ 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]
第一个切片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)
}
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
}
}
}
大概有如下特征:
- 首先判断,如果新申请的容量(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))
}
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)
}
函数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)
}
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)
}
切片做函数参数的时候,是引用传递,当函数对切片进行操作之后,切片的内容也会随之改变。
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)
}
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)
}
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)
}
Powered by Waline v2.9.1