前言
今天本来想去外地玩耍,结果睡过头错过了动车,只好总结一下slice,希望能与slice之间做一个了断。
文章由浅入深,遵从能用代码说话就不bb的原则。
正文
1.基本操作
1.1 声明
var stringSlice []string
stringSlice := []string{"咖啡色的羊驼"}
var intSlice []int64
intSlice := []int{18}
和数组的区别:就是**[]括号里头不加东西**。
初始化的的一些默认值:
func main() {
var stringSlice []string
var intSlice []int64
fmt.Printf("stringSlice ==> 长度:%v \t地址:%p \t零值是否nil:%v \n",len(stringSlice),stringSlice, stringSlice==nil)
fmt.Printf("intSlice ==> 长度:%v \t地址:%p \t零值是否nil:%v",len(intSlice),intSlice, intSlice==nil)
}
这里需要注意的是:slice的key必须是数字 && 0开始逐渐增加
1.2 增删改查
// 增
func add(slice []interface{}, value interface{}) []interface{} {
return append(slice, value)
}
// 删
func remove(slice []interface{}, i int) []interface{} {
return append(slice[:i], slice[i+1:]...)
}
// 改
func update(slice []interface{}, index int, value interface{}) {
slice[index] = value
}
// 查
func find(slice []interface{}, index int) interface{} {
return slice[index]
}
这里需要注意的是:
1.slice的增加需要依赖于append,这里会涉及到扩容机制(后文会说)
2.删除的话,只能是通过切割的方式重拼了,由于slice是引用类型,存的是指针,性能上不会有太多影响
1.3 插入 & 遍历 & 清空
// 插入
func insert(slice *[]interface{}, index int, value interface{}) {
rear := append([]interface{}{}, (*slice)[index:]...)
*slice = append(append((*slice)[:index], value), rear...)
}
// 遍历
func list(slice []interface{}) {
for k, v := range slice {
fmt.Printf("k:%d - v:%d", k,v)
}
}
// 清空
func empty(slice *[]interface{}) {
*slice = append([]interface{}{})
// *slice = nil
}
1.3 复制
// 复制
func main() {
intSlice := []int{1,2,3,4,5,6}
copySlice1 := make([]int,0,10)
_ = copy(copySlice1,intSlice)
fmt.Printf("长度为0的时候:%v\n",copySlice1)
copySlice2 := make([]int,6,10)
_ = copy(copySlice2,intSlice)
fmt.Printf("长度为6的时候:%v",copySlice2)
}
这里需要注意的是:要保证目标切片有足够的大小,注意是大小,而不是容量。
2.slice的深入了解
2.1 slice的基础数据结构 & 图
slice的基础数据结构:
type slice struct {
array unsafe.Pointer
len int
cap int
}
这里的array需要单独说下,这里是指针类型,也说明了slice是引用类型。
在slice底层,指针指向的是另一个数组。
还是有必要看一下源码中的实现:
// 创建一个slice
func makeslice(et *_type, len, cap int) slice {
// 检查目标类型的最长长度,slice的len和cap都必须小于这个值
maxElements := maxSliceCap(et.size)
if len < 0 || uintptr(len) > maxElements {
panic(errorString("makeslice: len out of range"))
}
// !!! len必须<= cap !!!
if cap < len || uintptr(cap) > maxElements {
panic(errorString("makeslice: cap out of range"))
}
// 申请内存
p := mallocgc(et.size*uintptr(cap), et, true)
// 返回一个slice
return slice{p, len, cap}
}
2.2 slice切割的底层变化
func main() {
x:=[]int{1,2,3,4}
y:=x[1:4]
fmt.Println(y)
}
// 输出:[2 3 4]
这里需要注意的是:切割遵从左开右闭的原则,就是[1:4],取得是第二个元素到第四个以下的,不包括第四个
来一波图解:
2.3 slice的扩容机制 & 实战例子
slice扩容机制还是比较有意思的,上源码:
func growslice(et *_type, old slice, cap int) slice {
...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}
...
return slice{p, old.len, newcap}
}
前文很多操作都是基于append的,那么slice在append的时候,如果发生了扩容,那么底层的数组会重建,同时拷贝老的数据到新数组里头。
举个例子:
func main() {
initSlice := []int{1}
// 进行扩容到2
initSlice = append(initSlice, 2)
// 进行扩容到4
initSlice = append(initSlice, 3)
x := append(initSlice, 4)
y := append(initSlice, 5)
fmt.Println(initSlice, x, y)
}
会输出:
[1 2 3] [1 2 3 5] [1 2 3 5]
图解说明:
根据前文介绍的扩容机制,initSlice的扩容轨迹是1-2-4。而slice只是引用类型,所以x和y只是copy了initSlice的指针,他们三个都是指向同一个底层数组,所以最后第四个坑被y给覆盖了。
再举一个正好遇到扩容时候的例子:
我们知道扩容时候是会生成新的底层数组,然后拷贝老的数组值。
func main() {
initSlice := []int{1}
// 此时扩容1-2,并且全部装满
initSlice = append(initSlice, 2)
// 以下任一append都会引发扩容
x := append(initSlice, 3)
y := append(initSlice, 4)
fmt.Println(initSlice, x, y)
}
输出:
[1 2] [1 2 3] [1 2 4]
图解:
由于都遇到了扩容,所以x与y各自另立门户,新建数组,slice指向的底层数组也不同了所以互不干扰了。
3.注意点
3.1 函数传参
func main() {
initSlice := []int{1,2,3}
fmt.Printf("刚开始时候:%v\n",initSlice)
doSomeThing(initSlice)
fmt.Printf("一番操作后:%v\n",initSlice)
}
func doSomeThing(s []int) {
s[0]=88
s = append(s, 10)
fmt.Printf("函数返回前:%v\n",s)
}
输出:
刚开始时候:[1 2 3]
函数返回前:[88 2 3 10]
一番操作后:[88 2 3]
这里面的变化情况抓住一个点:就是发送扩容时候底层数组是新建的!