Go八股图文解读||切片篇

切片是什么

切片是如下定义的:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

也就是说,他由3部分组成:指向实际地址的指针(8字节),代表长度和容量的int(各8字节),总计24字节,这部分定义十分清晰,简单易懂,长度len表示切片里面存放了多少数据,容量cap表示能存放多少数据,分配存储空间时便是根据cap的大小来分配的

那切片是怎么声明的?有如下几种方式:

var s1 []int //预设为nil
var s2 =make([]int,5) //长度为5,不指定容量默认与长度相同
var s3 =make([]int,5,7) //长度为5,容量为7
s4:=[]int{1,2,3,4} //自动识别设定长度、容量都为4

这里面有一个误区是,如果为切片声明了长度那么他会自动为里面的元素填充默认值,比如s2里面就是5个0,再进行append操作时会直接加在这5个0后面。此外,我之前还经常分别不清数组和切片的声明方式,如下:

如果声明的时候规定了大小的话便是数组,之后的大小便不能更改了,如果没有规定便是切片,但通常我们还是会使用make来创建切片,毕竟make是只为切片,map,channel设立的“专利”

切片的截取和扩容

切片的截取如下所示:

n[1:4]表示截取切片n的下标0到3(1,2,3)赋值给n1,“开始位置 : 结束位置”满足左开右闭的原则,如果冒号左右两边有一端没写既表示到一端,如 n2:=n[:4] 表示从下标0开始到下标3赋值给n2。

在这里需要注意的是,切片截取之后仍指向同一个底层数据,这时如果对n1的数据进行修改n的中数据也会被改变

同样对切片进行复制时,复制出来的切片也是指向同一分数据的,接下来讲一下扩容

提到扩容就不得不提 append() 方法,我们一般这样使用的:n=append(n,1,2,3) 其中的第一个参数n为目标切片,而第二个开始为在切片末端添加的元素,最后把这组合到一起重新赋值给n,我之前不太理解为什么要传一个目标切片这样的参数,后来我才了解不用被他这个名字误导,append() 方法不仅仅是追加元素,实际上还可以实现删除元素的操作,比如:

n=append(n[:i],n[i+1:]...)

这里就用到之前说的截取操作,解释一下,把n的第i+1个元素开始到最右端的部分 加到 n的最左端到i-1个元素末尾,这样的话第i个元素就被我们删除了,这里面需要注意左开右闭的问题,同时第二个参数后面有三个点,表示截取的全部元素;这个是必须要加上的,可以理解为,第二个元素开始的后面表示直接加入目标切片的元素,要满足切片定义的类型如n的int,而直接传入切片显然不属于int这一类型。

没错切片的删除元素就是这样复杂,一开始可能不太理解后面用得多了就熟练了,我也不太理解为什么不使用指定下标的方式来删除,只能说是特性吧

扩容机制

如果你找过相关的八股文,那么应该比较容易的答出,在go的1.18版本之前切片如果小于1048时每次2倍扩容(2倍装不下的话就用和容量作为新的容量),如果大于等于1024则每次循环扩容1/4直到装的下,在1.18之后将界限1024改成了256,同时大于等于256时每次扩容1/4+192(默认值*3/4也就是256的3/4),但其实这不完全准确

以这个为例,我使用的版本是1.22,原切片为257大于256,同时添加200个元素后,预计切片应该为257*5/4+192大约514左右,而实际上却是608,我看过很多关于这点的八股文,很少有讲这是因为什么,我一开始以为是由于span的67种等级的原因,会找一个可以放得下的span做为新容量,但实际上是因为内存对齐等。

可以简单理解为,他会首先根据上述的计算公式计算需要的容量,然后寻找稍大于他的go自己内部预先申请的一块内存块来作为新的容量,具体一点可以看这篇文章https://yufengbiji.com/posts/golang-slice

若再详细就需要去阅读源码了,go的源码其实有很多注释但都是英文(en...)而且十分庞杂,这部分代码在src下的mksizeclasses.go

额外部分

切片地址

在我验证截取切片地址时不小心弄错了然后发现:我去,这样不一样啊。实际上这个打印的地址是切片的地址而不是切片底层数据的地址,如果想要打印数据地址可以直接使用&n[0] 打印首个数据地址

然后发现:

我去,还不一样。当然的,n1是从n的下标1开始截取的,第一个数据正好差了一个int(8个字节)的大小,把n[1,3]变成n[0,3]就可以了。

切片截取补充

切片的截取实际上可以在[ ]里终止位置的末尾再添加一个分号和数值的,比如: n1=n[1:3:3] 那这是什么意思?

最后面的那个数字3代表所截切片的最大容量设置为n的索引3之前(既n1的值不会超过或等于n的索引3(对应数字4)),如果不设定这个值则会如下所示:

虽然n1只截了2个元素,但容量为原容量长度-n1切片开始处对应n的索引(len(n)-n1_start)也就是6-1,如果加上限定(n[1:3:3])则容量为 3 - 开始位置(max-n1_start)=2

如下:4-(0下标开始)=4

使用这种方式来进行截取目的是为了对append进行控制,就是如果对n1切片进行append操作向末尾加入一个值,这时如果这个值没有超过或等于限定(如图加入元素后索引下标为3对应n中的4)则会直接进行修改,此时的n和n1底层那个数据便被修改为了 [1 2 3 1 5 6],但是如果限定为3,则如下图所示

n1的操作超过了给其规定的界限,于是会对n1和n进行分割,将n1包含的数据进行复制另外存放其他位置, 此时n和n1便不再指向同一份数据了,可能理解起来有点绕,建议自己动手试一下。

copy方法

使用copy方法,可以将切片底层的数据完整赋值到另一个切片的底层,使用方法如下

其中,copy的第一个参数为目标切片,第二个参数为源切片,与append不同的是他会直接把n的值赋给n1而不需要再通过返回值来赋值,而他的返回值代表复制的元素个数,复制多少元素遵循 min(目标切片长度,源切片长度) 。这个例子中目标切片较小为3,所以复制3个元素

注意copy进行复制时不会对切片进行扩容,这部分了解即可,如下:

我们先声明一个长度3,容量5的n1,然后截取n1[:5](相当于一个指向n1数据的长度为5的临时切片)再进行copy,这时便会拷贝5个数据,但是由于n1的长度为3,所以只能看见3个数据,然后通过截取操作相当于把n1的长度扩大到与容量相等,再进行参看便能看到全部5条数据了

#牛客创作赏金赛#
Go八股文小解 文章被收录于专栏

Lotalot你干了什么?!没有golang八股文我们如何抗衡双招,Lotalot淡笑一声:“很简单,我自己写不就是了”说完,他气息终于不再掩饰,显露而出,Go八股文小解!

全部评论

相关推荐

qq乃乃好喝到咩噗茶:院校后面加上211标签,放大加粗,招呼语也写上211
点赞 评论 收藏
分享
评论
1
4
分享

创作者周榜

更多
牛客网
牛客企业服务