我又写“bug”了,关于go切片的探究
携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
哎,基础不牢,地动山摇。基础的问题,放在复杂的代码环境中,很难让人摸住头脑,而抽象出来,又如此简单。
将问题抽象出来
最近在用go
写定时任务的时候,采取了切片的方式作为数据的存储载体。但是在编写的过程中遇到了一个"bug",我们将问题抽象一下,以便更快的找到问题。
我们的需求
已有切片的数据:
我们想要插入一个数据6
,将其放置到5
和7
之间。
最后得到的切片是
如上简单的需求,我们很快变便写好了代码,代码如下:
s1 := []int{1,3,5,7,9}
newVal := 6
k := 3
temp := s1[k:]
s1 = append(s1[:k],newVal)
s1 = append(s1,temp...)
如上代码,我们先申请一个切片,数据为: 1 3 5 7 9
, 而后我们定义新的变量newVal
为6
。
此时我们想实现插入的动作,具体如何操作呢,我们操作步骤如下:
temp := s1[k:]
我们将数组以k
切割出来,k
为3
,即将7,9
抓出来,存入到变量temp
中,
而后使用append
串起来,理想状态是这样的。
看代码,也和我们想象的一样,我们最后添加一个打印s1
切片的语句,并且我们运行下代码尝试一下。
探寻底层原理
上面的代码执行后结果肯定不符合我们预期,应该是我们基础不牢固导致的,看来我们得有必要学习一下切片的基础知识了。
所谓的切片,也称之为可变长的数组,由三个属性构成: 起始地址、长度 和 容量。
其中go
给我们提供了一下几种方法来获取长度和容量的。
len
: 获取有效长度。
cap
: 获取容量。
我们来看下,我们如上代码的长度和容量分别是多少。
s1 := []int{1,3,5,7,9}
fmt.Println("长度为:",len(s1),"容量为:",cap(s1))
我们执行下
我们画一下关系图
我们发现,容量和长度都是5,也就意味着,我们只需要append
一个数据,底层数组就可以重新申请。
我们来尝试下
s1 := []int{1,3,5,7,9}
fmt.Printf("扩容前 数组地址为: %p 长度为: %d 容量为 %d\n",s1,len(s1),cap(s1))
s1 = append(s1,10)
fmt.Printf("扩容后 数组地址为: %p 长度为: %d 容量为 %d\n",s1,len(s1),cap(s1))
有了如上关于切片的基础知识,我们来推理一下我们为什么会引发问题呢?
我们将代码贴图在此处,便于我们分析。
首先,我们创建了一个数组 ,其值为: 1 3 5 7 9
接着我们temp
映射在了s1[3:]
处,此处要记住数组下标哦。用图示如下:
而后我们新增了数据6
,图示如下
最后,我们再将temp
的值追加到s1
之后。
那么,请问此时temp
是7
和9
还是 6
和9
呢?
由于是对数组的引用,所以它会取s1[3]
和s1[4]
,故值为6
、9
,我们追加后,最后的结果是图示如下:
这就是最后的结果。
尝试解法
我们已经复习了go
切片的相关知识,那么这个问题修改起来应该得心应手才对,我们来尝试下呢。
我们知道,在同一个数组下,我们想完成这个功能,还是稍微有点复杂的。所以我们就完完全全申请一个新的底层数组好了呀,我们可以这样写:
s1 := []int{1,3,5,7,9}
newVal := 6
k := 3
// 定义了一个新的切片
var temp []int
temp = append(temp,s1[k:]...) // 将数据放入新的切片中
s1 = append(s1[:k],newVal)
s1 = append(s1,temp...)
fmt.Println(s1)
如上代码,我们执行一下,查看结果
功能已经实现了,非常赞。
总结
有句老话说得好,基础不牢,地动山摇。我们之所以出现了该问题,是我们以为我们定义了一个切片,就能完成“独自占有”数组,殊不知我们得到的还是原有数组的引用,这本来是基础知识,哎,栽倒坑里了,正所谓,温度而知新,还是要把基础打牢固,多拿出来学习才行,怎么样,切片好玩吧,快来尝试一下吧。