我又写“bug”了,关于go切片的探究

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情

哎,基础不牢,地动山摇。基础的问题,放在复杂的代码环境中,很难让人摸住头脑,而抽象出来,又如此简单。

将问题抽象出来

最近在用go写定时任务的时候,采取了切片的方式作为数据的存储载体。但是在编写的过程中遇到了一个"bug",我们将问题抽象一下,以便更快的找到问题。

我们的需求

已有切片的数据:

我们想要插入一个数据6,将其放置到57之间。

最后得到的切片是

如上简单的需求,我们很快变便写好了代码,代码如下:

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 , 而后我们定义新的变量newVal6

此时我们想实现插入的动作,具体如何操作呢,我们操作步骤如下:

temp := s1[k:]我们将数组以k切割出来,k3,即将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))

image.png

有了如上关于切片的基础知识,我们来推理一下我们为什么会引发问题呢?

我们将代码贴图在此处,便于我们分析。

首先,我们创建了一个数组 ,其值为: 1 3 5 7 9

接着我们temp映射在了s1[3:]处,此处要记住数组下标哦。用图示如下:

而后我们新增了数据6,图示如下

最后,我们再将temp的值追加到s1之后。

那么,请问此时temp79 还是 69呢?

由于是对数组的引用,所以它会取s1[3]s1[4],故值为69,我们追加后,最后的结果是图示如下:

这就是最后的结果。

尝试解法

我们已经复习了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)

如上代码,我们执行一下,查看结果

功能已经实现了,非常赞。

总结

有句老话说得好,基础不牢,地动山摇。我们之所以出现了该问题,是我们以为我们定义了一个切片,就能完成“独自占有”数组,殊不知我们得到的还是原有数组的引用,这本来是基础知识,哎,栽倒坑里了,正所谓,温度而知新,还是要把基础打牢固,多拿出来学习才行,怎么样,切片好玩吧,快来尝试一下吧。

posted @ 2022-08-18 22:39  pdudos  阅读(0)  评论(0编辑  收藏  举报  来源