6 切片
-
数组长度是固定的,数组的长度也属于类型的一部分,有许多局限性
- 例如,函数传参时只能支持一种数组类型
- 例如,不能再继续向数组中添加元素
-
Slice
是一个拥有相同类型元素的可变长度序列- 是基于数组类型做的一层封装
- 十分灵活,支持自动扩容
-
切片是一个 引用类型,不支持直接比较,只能和
nil
比较- 内部结构包含
地址
、长度
、容量
- 内部结构包含
-
一般用于快速的操作一块数据集合
切片的定义
-
基本语法:
var name []T
- name : 变量名
- T : 切片中的元素类型
-
示例:
-
var a []string var b = []int{} var c = []bool{false, true}
-
切片的长度和容量
- 切片拥有自己的长度和容量:
len()
求长度cap()
求容量
切片的表达式
- 切片表达式 从
字符串、数组、指向数组或切片的指针
构造 子字符串或切片 - 两种变体
- 指定
low
和high
两个索引界限值的简单形式 - 除了
low
和high
索引界限值外,还指定容量 的完整形式
- 指定
简单切片表达式
-
切片的底层就是一个数组
-
可以基于 数组 通过切片表达式得到切片
-
[low , high)
: 左包含,右不包含-
得到的切片长度
high - low
-
得到的切片容量是 操作数数组下标从
low
到最后一个元素所组成的数组的长度 -
示例:
-
a := [5]int{1,2,3,4,5} s := a[1:3] fmt.Pritf("s:%v len(s):%v cap(s): %v",s,len(s),cap(s))
-
-
-
切片表达式中的任何索引可以省略:
low
默认为 0high
默认为 切片操作数的长度a[2:]
:high
= len(a)a[:3]
:low
= 0a[:]
low = 0, high = len(a)
-
注意:
- 对于数组和字符串:若
0<=low<=high<=len(a)
则索引合法,否则索引越界,运行时会panic
- 对 切片 再执行切片表达式(切片在切片时),
high
的上限是 切片的容量cap(s)
,而非长度len(s)
- 常量索引必须是非负,int类型
- 对于数组和字符串:若
完整切片表达式
- 数组,指向数组的指针,或切片 支持完整切片表达式
- 注意,字符串不支持
- 格式
a[low : high : max]
- 构造与简单切片表达式
a[low,high]
- 相同类型、相同长度、相同元素
- 将切片的容量设置为
max-low
- 在完整切片表达式中,只有第一个索引值
low
可以省略,默认为 0- 示例
a := [5]int{1,2,3,4,5}
t := a[1:3:5]
//t: [2 3] len(t):2 cap(t):4
- 完整切片表达式 需满足
0 <= low <= high <= max <= cap(a)
使用 make() 函数构造切片
-
除了基于数组构造切片,还可以动态创建切片
-
make 格式:
make ([]T, size, cap)
T
: 切片内元素类型size
:切片中元素数量cap
:切片容量
-
示例:
-
a := make([]int, 2, 10) //a: [0 0] len(a):2 cap(a): 10
-
a切片的内部存储空间分配了10,实际上只用了2个
-
容量不会影响当前元素的个数
-
切片的本质
-
**本质:**对底层数组的封装,包含三个信息
- 底层数组的指针
- 切片的长度
- 切片的容量
-
例如
a := [8]int{0,1,2,3,4,5,6,7} s1 := a[:5]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBXK9Zgp-1618978669363)(E:\LearningNotes\Go\GoLearning.assets\slice_01.png)]
- 切片
s2 := a[3:6]
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbwgJpKk-1618978669365)(E:\LearningNotes\Go\GoLearning.assets\slice_02.png)]
判断切片是否为空
- 最好使用切片的长度
len(s)==0
来判断
切片不能直接比较
-
不能使用
==
操作符判断切片是否全部元素相等 -
切片唯一合法的比较操作:和
nil
比较 -
一个
nil
值的切片,没有底层数组- 切片长度和容量都是 0
-
但是一个长度和容量都是0的切片不一定是
nil
-
示例
-
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
-
-
因此判断切片是否为空主要用
len(s)==0
切片的赋值拷贝
-
一个切片 拷贝赋值 另一个切片,两个切片会共享底层数组,因此对一个切片的修改会影响另一个切片
-
示例
-
s1 := make([]int, 3) //[0 0 0] s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组 s2[0] = 100 fmt.Println(s1) //[100 0 0] fmt.Println(s2) //[100 0 0]
-
切片遍历
- 支持下标索引遍历和
for range
键值遍历
append() 方法为切片添加元素
-
Go 中的内置函数
append()
可以为切片动态添加元素- 添加一个:
s = append(s,1)
- 添加多个:
s = append(s,1,2,3)
- 添加另一个切片中的元素 :
s = append(s, s2...)
- 添加一个:
-
==注意:==通过var声明的
零值切片
可以在append()
直接使用,无需初始化-
var s []int //声明后直接使用 s = append(s,1,2)
-
-
每个切片指向一个底层数组,
- 这个数组的容量够用就直接在该数组新增元素
- 当容量不够用时,切片按照一定的规则进行扩容。此时该切片指向的底层数组会更换
- 扩容操作发生在
append
时,因此需要用原变量接收函数返回值
-
append
:- 将元素追加到切片的最后并返回该切片
- 切片扩容的规则都是扩容前的两倍
切片的扩容策略
-
查看源码:
-
newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }
-
首先判断,若新申请(cap)大于两倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量
-
否则判断,若旧切片的长度小于1024,最终容量就是旧容量的两倍,
-
否则判断,若旧切片的长度大于等于1024,最终容量从旧容量开始循增加原来的1/4,直到最终容量大于等于新申请的容量
-
如果最终容量计算值溢出,则最终容量就是新申请的容量
-
-
-
注意:切片扩容会根据切片中元素类型的不同而作不同处理
使用 copy() 函数复制切片
-
由于切片是引用类型,拷贝赋值时,两个切片都指向同一块内存地址
-
示例
-
a := []int{1, 2, 3, 4, 5} b := a fmt.Println(a) //[1 2 3 4 5] fmt.Println(b) //[1 2 3 4 5] b[0] = 1000 fmt.Println(a) //[1000 2 3 4 5] fmt.Println(b) //[1000 2 3 4 5]
-
-
可以使用内置函数
copy()
迅速地将一个切片的数据复制到另外一个切片空间中,-
copy(destSlice, srcSlice []T)
- srcSlice:数据来源切片
- dextSlice:目标切片
-
示例:
-
a := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, a) //使用copy()函数将切片a中的元素复制到切片c fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 1000 fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1000 2 3 4 5]
-
从切片中删除元素
-
Go 中无删除切片元素的专用方法,需要使用切片本身特性:
-
若要删除下表为
index
:s = append(s[:index],s[index+1:]...)
-
func main() { // 从切片中删除元素 a := []int{30, 31, 32, 33, 34, 35, 36, 37} // 要删除索引为2的元素 a = append(a[:2], a[3:]...) fmt.Println(a) //[30 31 33 34 35 36 37] }
-
练习题
练习一
请写出下面代码的输出结果。
func main() {
var a = make([]string, 5, 10)//a : [0 0 0 0 0 0 0 0 0 0 ],len 5, cap 10
for i := 0; i < 10; i++ {
a = append(a, fmt.Sprintf("%v", i)) //[0 0 0 0 0 "0" "1" ..."9"]
}
fmt.Println(a)
}
- 直接分析:[0 0 0 0 0 “0” “1” …“9”]
- run :
[ 0 1 2 3 4 5 6 7 8 9]
- 前面五个空字符串
练习二
请使用内置的sort
包对数组var a = [...]int{3, 7, 8, 9, 1}
进行排序(附加题,自行查资料解答)。
-
sort
包主要针对[]int., []float64,[]string 以及其他自定义切片的排序-
内部实现了:插入排序、归并排序、堆排序、快速排序
-
sort 会依据实际数据自动选择最优的排序方法
-
我们只需要考虑实现
sort.Interface
类型-
type Interface interface { Len() int // Len方法返回集合中的元素个数 Less(i, j int) bool // i>j,该方法返回索引i的元素是否比索引j的元素小、 Swap(i, j int) // 交换i, j的值 }
-
例如:
-
package main import ( "fmt" "sort" ) type IntSlice []int func (s IntSlice) Len() int { return len(s) } func (s IntSlice) Swap(i, j int){ s[i], s[j] = s[j], s[i] } func (s IntSlice) Less(i, j int) bool { return s[i] < s[j] } func main() { a := []int{4,3,2,1,5,9,8,7,6} sort.Sort(IntSlice(a)) fmt.Println("After sorted: ", a) }
-
-
-