Slice
Last updated
Was this helpful?
Last updated
Was this helpful?
站在我的角度来看, 正是因为Slice太好用了, 所以很多人最后忘记了有数组的存在了, 的确是啊, 大家都搞[]string+append
的方式来用, 好像确实找不到什么理由来使用数组. 如何创建出slice&array? 指明/不指明size就可以:
创建一个数组:
arr := [3]string{}
arr := [...]string{}
创建一个切片:
sli := []string{}
sli := make([]string, 10)
数组变切片:
sli := arr[:]
我们从最简单的开始想象, 如果想要存一组元素, 最简单的方式是什么? 是不是就是搞一小块内存出来存着就好咯? 没有指针, 没有滑头, 就一组数字, 存就完事儿
也正是因为数组 = 实体, 没有指针等滑头, 结合Golang函数传值(复制)的特点, 我们也能想象出上面会发生什么, 一个数组在传递的过程中, 他的值被拷贝了一份
与之相对的是slice并没有实体, 它只有一个指针, 因此在函数传递参数的时候传指针, 修改的是同一份内容.
来玩个游戏, 我们先创建一个数组, 然后通过"[:]"的方式创建出Slice, 好玩的:
那么他俩的地址是? 是同一个地址
那么修改数组, 对应的slice会怎样? 会跟着一起改
狗吧? 现在你知道为什么形参为[3]int{}
的函数不能接受[]int{}
的实参了吧? 因为一个是指针一个是实体嘛(偶偶, 这下我完全搞懂了!完全没懂)
继续玩! 之前在写Map的时候这么玩过一次, 这招能帮我们看到到底执行了什么函数, OK, 这里的make被翻译成了runtime.makeslice
:
make函数被编译器翻译成了makeslice函数,这个函数通过malloc, 以unsafe.Pointer的方式, 返回一个指向对应内存段的指针 (简单的理解就是void*
, 后面再cast成实际的类型)
我也不是嘴嗨, 上面这就是runtime里的定义, 想要创建一个slice需要先预估大小后创建一个数组指针(unsafe), 然后塞到slice结构体里才能变成你天天用的slice. 平时的len,cap什么的都有在搞吧?(压力马斯内!) 原理就是检查slice结构体里的变量
也不是一无是处, 数组的一个特点就是定长, 这使得编译阶段的越界检查成为可能, 尝试通过go build(不运行, 只编译)一下上面的片段, 你会发现连编都编不过
那么Slice呢, slice因为是指针也不定长因此编译期的越界管不到, 怎么办呢? 只能运行期内panic咯
思考以下问题: append返回什么? 答案很有趣, slice因为是指针, 所以也要考虑深拷贝浅拷贝问题, 这道题的答案是: 取决于你返回值是不是覆盖变量
如果覆盖, 则:
判断slice下数组容量是否足够, 是否需要扩容(growslice)
然后将这个数组重新给slice
如果不覆盖, 则:
判断slice下数组容量是否足够, 是否需要扩容(growslice)
如果需要扩容, 并分配一个新内存[深拷贝]
如果不需要扩容, 则新老两个指向同一个数组[浅拷贝]
初始化的slice只有1容量, 按照加倍增长的原则, 变成2/变成4, 每次扩容, 底层的那个数组就会重新分配一次, 因此地址也在跟着变, 最后在append2/3的时候因为容量够了, 所以不会重新分配内存
那么回到一个经典问题, 如果我改slice, new_slice会不会一起改呢? 看看上面两种情况下, 两个slice的地址.
扩容了分配了新数组, 不会一起改, 底层数组都不一样么
没扩容也没分配新数组, 一起改, 因为指向的是同一个底层数组
非常鬼畜对吧? 怎么办呢? 我现在就是想要一个新slice, 不要跟之前的有任何关联, 请指明使用深拷贝: make+copy