共计 2009 个字符,预计需要花费 6 分钟才能阅读完成。
slice
一、构造组成
切片实质上是一个构造体 slice,他的属性由上面三局部组成:
- array:元素存哪里
- len:曾经存了多少
- cap:容量是多少
type slice struct {
array unsafe.Pointer // 数组的指针
len int // 切片长度
cap int // 切片容量
}
举个例子
咱们都晓得,go 语言的办法传参数都是值传递。
因为切片类型是一个构造体,所以当传到 change 办法的时候,形参 arr 是 arrOri 一份正本。然而因为属性 array 是一个指针,所以扭转 arr 的值,也会影响到 arrOri
func main() {arrOri := []string{"0", "1", "2"}
change(arrOri)
fmt.Println(arrOri[0])
}
func change(arr []string) {if len(arr) > 0 {arr[0] = "test0"
}
}
//output:test0
二、append 产生了什么?
通过下面的例子咱们晓得,如果某个办法扭转的切片某个地位的值,也会影响到原切片。
上面咱们再看一下这个例子,为什么最初输入是 0,而不是 test0 呢?接下来咱们持续来聊聊 扩容
func main() {arrOri := []string{"0", "1", "2","3"}
change2(arrOri)
fmt.Println(arrOri[0])
}
func change2(arr []string) {arr = append(arr, "4")
if len(arr) > 0 {arr[0] = "test0"
}
}
//output:0
咱们最开始申明切片 arrOri 的时候给了 4 个元素,那切片的初始容量为 4,array 如下图的下面的数组。
但当咱们增加一个元素的时候,原先的数组的长度曾经不够咱们减少元素。
所以须要申明一个新的更长数组,把老数组的数据复制到新的数组中去,而后追加咱们须要增加的元素,最初把切片的属性 array 的指针指向新的数组。
所以 arrOri 的 array 属性指向的是老数组,而 arr 的 array 属性指向的是新的数组,扭转 arr 的值不会影响到 arrOri 的值。
上面是 append 是的源码解析:
// growslice 切片容量增长函数
// 扩容概括:1. 须要的 cap 大于 2 倍旧 cap -> 增长到新的 cap
// 2. 旧 cap 小于 256 -> 双倍增长
// 3. 旧 cap 大于 256 -> 1.25 倍 +192 的速度线性扩容,直到新切片的 cap 大于须要的 cap
// @param et 切片数据的类型
// @param old 老的切片
// @param cap 须要增长到的容量
// @return slice 返回新的切片
func growslice(et *_type, old slice, cap int) slice {// 切片的数据类型的大小为 0(比方 struct{})// 则数组能够不必具体的值,只须要扭转 len 和 cap 的值
if et.size == 0 {return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
// 如果【新的切片须要的 cap】>【老切片 cap】的两倍,则扩容到新的切片须要的 cap
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
// 如果原数组长度 < 256,容量翻倍
newcap = doublecap
} else {
// 如果原数组长度 >= 256,// 容量以【1.25*newcap + 192】线性速度速度缓缓扩容,直到 newcap 大于须要的 cap
for 0 < newcap && newcap < cap {newcap += (newcap + 3*threshold) / 4
}
if newcap <= 0 { // 原先容量为 0,则容量为参数 cap
newcap = cap
}
}
}
var overflow bool
var lenmem, newlenmem, capmem uintptr
// 调配对象
lenmem = uintptr(old.len) * et.size
p = mallocgc(capmem, et, true)
// 把旧数据 old.array 复制到新数组 p 中
memmove(p, old.array, lenmem)
return slice{p, old.len, newcap}
}
三、截断产生了什么
func main() {a := []int{1, 2, 3, 4, 5, 6}
b := a[2:5]
a[2] = 999
fmt.Println(b)
}
//output:[999 4 5]
当咱们进行切片截断的时候,新切片 b 的 array 属性指针和 a 的 array 指针指向同一个数组(如下图),只是 len 和 cap 属性值不同,所以扭转 a 的值会影响到 b 的数据,如下图
下面相干代码基于 go1.18
https://github.com/golang/go/…
正文完