go语言之切片
Slice的结构如下
type slice struct {
array unsafe.Pointer
len int
cap int
}
结构非常简单,只有三个部分:
array: 指向数组的指针。
len:当前长度。
cap:容量
Slice的初始化
var ints []int
变量ints实际上就由以下三个部分组成。
slice的元素要存在一段连续的内存中,实际上就是一个数组,但是目前只分配了这个切片结构,还没有分配底层数组。所以 data = nil | len = 0 | cap
= 0 。
通过make来定义
var ints []int = make([]int, 2, 5)
这时不仅会分配这三部分结构,还会开辟一段内存作为切片的底层数组,这里make会为ints开辟一段容纳5个整型元素的内存,还会把它们初始化为整型的默认值0 。
但是目前这个slice变量只存储了两个元素,所以data指向这个底层数组的首地址 | len = 2 | cap = 5 。
这个时候我们添加一个元素,然后再做一个赋值。
ints = append(ints, 1)
ints[0] = 1
append的元素会被自动添加到第3个位置。
已经存储的元素是可以安全读写的,但是超出这个范围就属于越界访问。会发生panic
Slice的底层数组
int型slice的底层就是int型数组,string型slice的底层就是string型数组。
但是slice中的数组指针,并不是必须指向底层数组的开头。
arr := [10]{0,1,2,3,4,5,6,7,8,9} //数组容量声明了就不可改变
我们可以把不同的slice关联到同一个数组。
var s1 []int = arr[1:4] //左闭右开
var s2 []int = arr[7:]
slice访问和修改的其实都是底层数组的元素。
如果要给s1添加两个元素,直接使用append即可,这个底层数组依然可以使用。
但是如果要给s2添加元素,这个底层数组就不能再使用了,因为数组的大小是固定的。
因此,得开辟一个新的数组。原来的元素得拷贝过来,还得添加新的元素。元素个数改为4,容量扩到了6。
Slice的扩容规则
预估规则:
1 如果扩容前容量翻倍,还是小于所需的最小容量,那么预估容量就等于所需的最小容量。
2 否则就要再细分:
(a) 如果扩容前元素个数小于1024,那就直接翻倍。
(b) 如果扩容前元素大于等于1024,那就先扩容至原来的 1/4
再来看go专家编程里面的这道题
func SliceRise(s []int) {
s = append(s, 0)
for i := range s {
s[i]++
}
}
func main() {
s1 := []int{1, 2}
s2 := s1
s2 = append(s2, 3)
SliceRise(s1)
SliceRise(s2)
fmt.Println(s1, s2)
}
输出结果是[1,2][2,3,4]
1 首先s1在初始化的时候,分配了一个底层数组,len=2,cap=2 ;
将s1赋值给s2,两者就指向了同一个底层数组;
2 s2发生扩容,因为cap不够了,这个时候s2指向一个新的底层数组,并且len=3,cap=4 ;
然后调用两次SliceRise函数;
3 s1作为参数进入函数时,发生了扩容,因为cap不够了,所以新分配了一个底层数组,这个时候,main函数中的s1与SliceRise中的s1已经分道扬镳了。所以main函数中的s1不会有任何改变;
4 s2作为参数进入函数时,同样发生了扩容,但是cap还够,所以不会分配新的底层数组,接下来的所有改变都会影响到main函数中的s2;
但是为什么s2输出[2,3,4] 而不是[2,3,4,1]呢。原因在于SliceRise中s和s2虽然指向了同一个底层数组,但其实不是一个结构体。通过打印s和s2的地址就可以看到。s的len=4,cap=4, 而s2的len=3,cap=4,由于len不一样,因此s2只能取前面3个的数据。
所以最终在main函数中,s1输出[1,2],而s2输出[2,3,4]。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架