在上篇数据类型-Array中写到因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性
1
2
3
4
5
6
7 func arraySum(x [5]int) int{
sum := 0
for _, v := range x{
sum = sum + v
}
return sum
}这个求和函数只能接受
[5]int
类型,其他的都不支持。 再比如,
1 a := [5]int{1, 2, 3, 4, 5}数组a中已经有五个元素了,我们不能再继续往数组a中添加新元素了。
切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:
- 底层数组的指针
- 切片的长度(len)
- 切片的容量(cap)
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
,切片s1 := a[:5]
,相应示意图如下。
切片s2 := a[3:6]
,相应示意图如下:
切片的定义
1 |
// 初始化定义 |
var 声明切片
1 |
package main |
Make 初始化切片
1 |
package main |
判断切片是否为空
要检查切片是否为空,请始终使用len(s) == 0
来判断,而不应该使用s == nil
来判断。
切片不能直接比较
切片之间是不能比较的,我们不能使用==
操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil
比较。 一个nil
值的切片并没有底层数组,一个nil
值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil
,例如下面的示例:
1 |
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil |
所以要判断一个切片是否是空的,要是用len(s) == 0
来判断,不应该使用s == nil
来判断。
切片的赋值拷贝
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
1 |
package main |
切片遍历
切片的遍历方式和数组(Array)是一致的,支持索引遍历和for range
遍历。
1 |
func main() { |
切片添加元素
Go语言的内建函数append()
可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
1 |
目标变量 = append(需被加入切片的变量名, 需追加的常量或者切片的变量名) |
1 |
func main(){ |
注意:通过var声明的零值切片,在append()
函数中可直接使用,无需初始化。
1 |
// 可以这样做,但没必要 |
切片底层内存原理探究
引入
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在
append()
函数调用时,所以我们通常都需要用原变量接收append函数的返回值。从上面的结果可以看出:
append()
函数将元素追加到切片的最后并返回该切片。- 切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。
源码解读
$GOROOT/src/runtime/slice.go
1 |
// Copyright 2009 The Go Authors. All rights reserved. |
内存分配部分,重点部分
1 |
newcap := old.cap |
- 首先判断,如果新申请容量(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})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如
int
和string
类型的处理方式就不一样。
复制切片
1 |
// 疑问 |
Go语言内建的copy()
函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()
函数的使用格式如下:
1 |
copy(destSlice, srcSlice []T) |
示例如下
1 |
func main() { |
删除元素
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
1 |
func main() { |
总结:要从切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
总结及注意点
- 底层数组的指针、切片的长度(len)、切片的容量(cap)
var
与make
基于var,var定义的时仅会声明,不会申请内存。make初始化会分配内存。其内容为初始值。(string: 空、int:0、bool:false、Array:var时为nil\make时为”[]”
的内部有Len-1个0)- 通过var声明的零值切片可以在
append()
函数直接使用,无需初始化。 - Append可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面需要加…)。
- 当内存小于1024时,每次扩宽两倍。当1024每次增加原本的1/4倍
- 要从切片a中删除索引为
index
的元素,操作方法是a = append(a[:index], a[index+1:]...)