Go语言基础之切片!

数组的局限:数组的长度是固定的,并且长度属于类型的一部分。

切片可以理解为长度可以动态变化的数组

  • 它是基于数组类型做的一层封装,非常灵活,支持自动扩容。

切片(Slice)是一个拥有相同类型元素的可变长度的序列。

切片是一个引用类型,它的内部结构包含地址长度容量

  • 切片一般用于快速地操作一块数据集合。

切片中可以容纳元素的个数称为容量,容量大于等于长度

  • 可以通过len(slice)cap(clice)分别获取切片的长度和容量。

可以通过make(type,len,cap)的方式创建出自动以初始长度和容量的切片

  • 在追加元素的过程中,如果容量不够用时,就存在动态扩容问题
    • 动态扩容采用的是倍增策略,即:新容量=2*就容量

扩容后的切片会得到一片新的连续内存地址,所有元素的地址都会随之发生改变。

切片的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var name []T

func main() {
// 声明切片类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化
fmt.Println(a) //[]
fmt.Println(b) //[]
fmt.Println(c) //[false true]
fmt.Println(a == nil) //true
fmt.Println(b == nil) //false
fmt.Println(c == nil) //false
// fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较
}

切片表达式

切片表达式从字符串、数组、指向数组或切片的指针构造字符串或切片,有两种变体:

  • 一种指定start和end两个索引界限值得简单形式
  • 另一种是除了start和end索引界限值外还制定容量的完整形式。

简单切片表达式:

切片的底层就是一个数组,可以基于数组通过切片表达式得到切片

  • 切片中的startend表示一个索引范围(左包含,右不包含)
1
2
3
4
5
6
func main(){
a := [5]int{1,2,3,4,5} //长度为5,数据类型为int的数组
s := a[1:3] // s:= a[start:end]
fmt.Printf("s:%v len(s):%v cap(s):%v\n",s,len(s),cap(s))
//s:[2 3] len(s):2 cap(s):4
}

为了方便,通常可以省略切片表达式中的任何索引

  • 省略了start则默认是0,省略了end则默认到结尾。
1
2
3
a[2:]  // a[2:len(a)]
a[:3] // a[0:3]
a[:] // a[0:len(a)]

完整切片表达式:

对于数组,指向数组的指针,或切片(注意不能是字符串)支持完整切片表达式。

1
a[start:end:max]

上面的代码会构造与简单切片表达式a[start:end]相同类型,相同长度和元素的切片

  • 另外,它会将得到的结果切片的容量设置为max-start

在完整切片表达式中只有第一个索引值(start)可以省略,默认为0。

1
2
3
4
5
6
func main(){
a := [5]int{1,2,3,4,5}
t := a[1:3:5]
fmt.Printf("t:%v len(t):%v cap(t):%v\n",t,len(t),cap(t))
// t:[2,3] len(t):2 cap(t):4
}

完整切片表达式需要满足的条件是0<=start<=end<=max<=cap(a)

  • 其他条件和简单切片表达式相同。

使用make()函数构造切片

上面都是基于数组来创建的切片

  • 如果需要动态的创建哪一个切片,就需要使用内置函数make()
1
2
3
4
make([]T,size,cap)
// T:切片的元素类型
// size:切片中元素的数量
// cap:切片的容量
1
2
3
4
5
6
func main(){
a := make([]int,2,10)
fmt.Println(a) // [0,0]
fmt.Println(len(a)) // 2
fmt.Println(cap(a)) // 10
}

a的内部存储空间已经分配了10个,但是实际上只用了2个,容量并不会影响当前元素的个数

  • 所以len(a)返回2,cap(a)则返回该切片的容量。

切片的本质

切片的本质就是对底层数组的封装,包含三个信息:

  • 底层数组的指针,切片的长度和切片的容量。

现在有一个数组a:=[8]int{0,1,2,3,4,5,6,7},切片s1:=a[:5]

相应的示意图如下:

img

切片s2:=a[3:6],相应的示意图如下:

img

切片的赋值拷贝

拷贝前后两个变量共享底层数组

  • 对一个切片的修改会影响另一个切片的内容。
1
2
3
4
5
6
7
func main(){
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遍历。

1
2
3
4
5
6
7
8
9
10
11
func main(){
s := []int{2,3,1}
for i:=0;i<len(s);i++{
fmt.Println(i, s[i])
}

for index,value:=range s{
fmt.Println(index,value)
}

}

向切片中添加元素

Go语言的内建函数append()可以为切片动态添加元素

  • 可以一次添加一个元素,可以添加多个元素,也可以添加另外一个切片中的元素
1
2
3
4
5
6
func main(){
var s []int
s = append(s, 1) //s[1]
s = append(s,2,3,4) // [1 2 3 4]
s = append(s, s...) // [1 2 3 4 1 2 3 4]
}

通过var声明的零值切片可以在append()函数直接使用无需初始化。

没有必要初始化一个切片再传入append()函数使用。

1
2
3
4
5
s := []int{}  // 没有必要初始化
s = append(s, 1,2,3)

var s = make([]int) // 没有必要初始化
s = append(s,1,2,3)

从切片中删除元素

Go语言中并没有删除切片元素的专用方法,可以通过使用切片本身的特性来删除元素。

1
2
3
4
5
6
func main(){
// 从切片中删除元素
a := []int{30,32,34,36,38}
// 删除索引为2的元素
a = append(a[:2],a[3:]...)
}

从切片a中删除索引为index的元素

  • 操作方法是a = append(a[:index], a[index+1:]...)