6 切片
6 切片
数组长度是固定的,数组的长度也属于类型的一部分,有许多局限性
- 例如,函数传参时只能支持一种数组类型
- 例如,不能再继续向数组中添加元素
Slice
是一个拥有相同类型元素的可变长度序列- 是基于数组类型做的一层封装
- 十分灵活,支持自动扩容
切片是一个 引用类型,不支持直接比较,只能和
nil
比较- 内部结构包含
地址
、长度
、容量
- 内部结构包含
一般用于快速的操作一块数据集合
切片的定义
基本语法:
var name []T
- name : 变量名
- T : 切片中的元素类型
示例:
- ```
var a []string
var b = []int{}
var c = []bool{false, true}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
#### 切片的长度和容量
* 切片拥有自己的长度和容量:
* `len()` 求长度
* `cap()` 求容量
#### 切片的表达式
* 切片表达式 从`字符串、数组、指向数组或切片的指针` 构造 子字符串或切片
* 两种变体
* 指定`low`和`high`两个索引界限值的简单形式
* 除了`low`和`high`索引界限值外,还指定容量 的完整形式
##### 简单切片表达式
* 切片的底层就是一个数组
* 可以**基于 数组** 通过切片表达式得到切片
* `[low , high)`: 左包含,右不包含
* 得到的**切片长度** `high - low`
* 得到的**切片容量是** 操作数数组下标从`low` 到最后一个元素所组成的数组的长度
* 示例:
* ```go
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- 示例
1 | a := [5]int{1,2,3,4,5} |
- 完整切片表达式 需满足
0 <= low <= high <= max <= cap(a)
使用 make() 函数构造切片
除了基于数组构造切片,还可以动态创建切片
make 格式:
make ([]T, size, cap)
T
: 切片内元素类型size
:切片中元素数量cap
:切片容量
示例:
```go
a := make([]int, 2, 10)
//a: [0 0] len(a):2 cap(a): 101
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
* a切片的内部存储空间分配了10,实际上只用了2个
* 容量不会影响当前元素的个数
#### 切片的本质
* **本质:**对底层数组的封装,包含三个信息
* 底层数组的指针
* 切片的长度
* 切片的容量
* 例如
```go
a := [8]int{0,1,2,3,4,5,6,7}
s1 := a[:5]切片
s2 := a[3:6]
判断切片是否为空
- 最好使用切片的长度
len(s)==0
来判断
切片不能直接比较
不能使用
==
操作符判断切片是否全部元素相等切片唯一合法的比较操作:和
nil
比较一个
nil
值的切片,没有底层数组- 切片长度和容量都是 0
但是一个长度和容量都是0的切片不一定是
nil
示例
```go
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!=nil1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
* 因此判断切片是否为空主要用`len(s)==0`
### 切片的赋值拷贝
* 一个切片 **拷贝赋值** 另一个切片,两个切片会共享底层数组,因此对一个切片的修改会影响另一个切片
* 示例
* ```go
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()
直接使用,无需初始化```go
var s []int //声明后直接使用
s = append(s,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
31
32
33
34
35
36
37
38
39
* 每个切片指向一个**底层数组**,
* 这个数组的**容量够用**就直接在该数组新增元素
* 当**容量不够用**时,切片**按照一定的规则进行扩容**。此时该切片指向的**底层数组会更换**
* **扩容操作**发生在`append`时,因此需要用原变量接收函数返回值
* `append`:
* 将元素追加到切片的最后并返回该切片
* 切片扩容的规则都是扩容前的两倍
### 切片的扩容策略
* 查看源码:
* ```go
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() 函数复制切片
由于切片是引用类型,拷贝赋值时,两个切片都指向同一块内存地址
示例
```go
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
* 可以使用内置函数`copy()`迅速地将一个切片的数据复制到另外一个切片空间中,
* `copy(destSlice, srcSlice []T)`
* srcSlice:数据来源切片
* dextSlice:目标切片
* 示例:
* ```go
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:]...)
```go
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]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
### 练习题
#### 练习一
请写出下面代码的输出结果。
```go
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
类型- ```go
type Interface interface {
}Len() int // Len方法返回集合中的元素个数 Less(i, j int) bool // i>j,该方法返回索引i的元素是否比索引j的元素小、 Swap(i, j int) // 交换i, j的值
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
* 例如:
* ```go
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)
}
- ```go