go切片截取细节分析

发布时间:2024年01月01日

?切片截取,有没有很迷?

目录

典型截取(两参数、三参数)及分析

迷之append参与截取及细节分析

关于截取时的初始索引是否从第一个位置开始的影响

修改原切片细节分析


典型截取(两参数、三参数)及分析

先看一个例子来表示一下切片的基础截取操作(仅截取,无append):

?? ?a := []int{1, 2}
?? ?a0 := a[0:1]
?? ?fmt.Printf("a0=%v, a0=%p, cap=%v\n", a0, a0, cap(a0)) // a0=[1], a0=0xc00009e070, cap=2
?? ?fmt.Printf("a=%v, a=%p, cap=%v\n", a, a, cap(a)) ? ? ?// a=[1 2], a=0xc00009e070, cap=2
?? ?
?? ?b := a[0:1:1]
?? ?fmt.Printf("a=%v, a=%p, cap=%v\n", a, a, cap(a))
?? ?fmt.Printf("b=%v, b=%p, cap=%v\n", b, b, cap(b)) ? ?// b=[1], b=0xc00009e070, cap=1

?? ?c := a[1:1]
?? ?fmt.Printf("a=%v, a=%p, cap=%v\n", a, a, cap(a))
?? ?fmt.Printf("c=%v, c=%p, cap=%v\n", c, c, cap(c)) ? ?// c=[], c=0xc00009e078, cap=1

?? ?d := a[0:2:2]
?? ?fmt.Printf("d=%v, d=%p, cap=%v\n", d, d, cap(d))

?? ?e := a[1:2:2]
?? ?fmt.Printf("e=%v, e=%p, cap=%v\n", e, e, cap(e))

?? ?f := a[0:1]
?? ?fmt.Printf("f=%v, f=%p, cap=%v\n", f, f, cap(f))
?? ?fmt.Println(reflect.DeepEqual(f, b)) ? ? // true

?? ?g := a[0:2]
?? ?fmt.Printf("g=%v, g=%p, cap=%v\n", g, g, cap(g)) ? ?// g=[1 2], g=0xc00009e070, cap=2
?? ?fmt.Println(reflect.DeepEqual(d, g)) ? ? // true?

?? ?fmt.Println(reflect.DeepEqual(a, b)) ? ? // false
?? ?fmt.Println(reflect.DeepEqual(a, d)) ? ? // true

结果:

a0=[1], a0=0xc00009e070, cap=2
a=[1 2], a=0xc00009e070, cap=2
a=[1 2], a=0xc00009e070, cap=2
b=[1], b=0xc00009e070, cap=1
a=[1 2], a=0xc00009e070, cap=2
c=[], c=0xc00009e078, cap=1
d=[1 2], d=0xc00009e070, cap=2
e=[2], e=0xc00009e078, cap=1
f=[1], f=0xc00009e070, cap=2
true
g=[1 2], g=0xc00009e070, cap=2
true
false
true

以上分析中包含了海量信息,能总结出方便理解的规律、能看透才是王道。?

从上面结果可以得到以下信息:
1,c和e的地址和其它的不同;
2,b虽然是一个新搞出来的切片,但b和a都是相同的地址,a底层的数组b也在用,同时b是从a切片第一个位置开始(首地址);
3,c截取的也是a的元素组成的切片,但c是从第2个位置开始的,c和a的地址不同;
4,d截取的也是a的元素组成的切片,且d是从数组第1个位置开始的,d和a的地址相同;
5,e截取的也是a的元素组成的切片,是从数组第二个位置开始的,e和a的地址不同,e和c的地址相同;
6,d的容量之所以为2,是因为截取时指定了截取的第三个参数为2,起始索引为0,则2-0=2,以此类推e的容量必然是1,因为2-1=1;
7,d := a[0:2:2]这个操作包含了a的所有元素,是从首地址开始,同时长度和容量都和a相同,因此DeepEqual的结果为true;
8,f和b容量不同,f和b的区别是截取的第三个参数一个指定了另一个是默认,b是1,f是2,那么容量不同为啥还能深度相等?
因为对于切片,类型相同、相同索引处元素相等,则深度相等;
9,对于a0 := a[0:1]这样的仅截取操作,根本不会影响a切片,但截取操作在append中就可能有影响了。

这个代码例子虽然看起来简单没什么复杂操作,但却包罗万象不乏细节,下面是结论:

1,截取之后得到的切片是不是和原切片指向的地址相同,主要是看截取的首地址是否是从原切片的第一个元素开始,因为都是基于a来截取,底层数组一直在那放着,就看不同的人要截取多少(即东西是同一份东西,不同的人看到的视图不一样而已);
2,以a[d:e]为例,是指基于a切片,从其索引为d的位置开始、索引为e的位置(不包括)结束所获得的切片,所得切片的容量是a切片的原有容量
3,以a[d:e:f]为例,是指基于a切片,从其索引为d的位置开始、索引为e的位置(不包括)结束所获得的切片,所得切片的容量是f-d。

基于纯截取而没有其它操作的情形下,原切片不会被修改,基于以上案例和分析,我们可以得到切片截取时的地址、容量等变化。

迷之append参与截取及细节分析

下面是四个典型案例,需要仔细观察。

函数1:

func cut95() {
?? ?fmt.Println("cut95 start")
?? ?a := []int{1, 2}
?? ?b := append(a[0:1], 3)
?? ?//fmt.Println(a[1:2]) // [3]
?? ?fmt.Printf("a= %v, %v, %p\n", a, cap(a), a) // a= [1 3], 2, 0xc00001a140
?? ?fmt.Printf("b= %v, %v, %p\n", b, cap(b), b) // b= [1 3], 2, 0xc00001a140
?? ?c := append(a[1:2], 4)
?? ?fmt.Printf("a= %v, %v, %p\n", a, cap(a), a) // a= [1 3], 2, 0xc00001a140
?? ?fmt.Printf("b= %v, %v, %p\n", b, cap(b), b) // b= [1 3], 2, 0xc00001a140
?? ?fmt.Printf("c= %v, %v, %p\n", c, cap(c), c) // c= [3 4], 2, 0xc00001a170

?? ?d := append(a[1:2], 4, 5, 6) ? ? ? ? ? ? ? ?// 有人说扩容了就会怎么怎么样,来让它扩容一下
?? ?fmt.Printf("a= %v, %v, %p\n", a, cap(a), a) // [1 3], 2, 0xc00001a140
?? ?fmt.Printf("b= %v, %v, %p\n", b, cap(b), b) // [1 3], 2, 0xc00001a140
?? ?fmt.Printf("c= %v, %v, %p\n", c, cap(c), c) // [3 4], 2, 0xc00001a170
?? ?fmt.Printf("d= %v, %v, %p\n", d, cap(d), d) // [3 4 5 6], 4, 0xc0000121c0
?? ?/*
?? ??? ?第1处截取操作:会导致a被修改
?? ??? ?第2处截取操作:a没有被修改
?? ??? ?第3处截取操作:a没有被修改
?? ??? ?注意除过第三次截取外其余截取之后的新切片均未发生扩容,和扩容无关;
?? ??? ?第三次截取扩容后地址发生了变化这是肯定了,因为容量需要扩充,原数组已不能满足要求、数组定长不能扩容,
?? ??? ?此时需要申请新数组(指针已变),再将数据拷贝到新切片;发生了扩容,但依旧原切片a没有变化。
?? ?*/
}

函数2:

func cut96() {
?? ?fmt.Println("cut96 start")
?? ?a := []int{1, 2}
?? ?b := append(a[1:2], 3)
?? ?//fmt.Println(a[1:2]) // [3]
?? ?fmt.Println("a= ", a) // [1 2]
?? ?fmt.Println("b= ", b) // [2 3]
?? ?c := append(a[1:2], 4)
?? ?fmt.Println("a= ", a) // [1 2]
?? ?fmt.Println("b= ", b) // [2 3]
?? ?fmt.Println("c= ", c) // [2 4]
?? ?d := append(a[0:1], 5)
?? ?fmt.Println("a= ", a) // [1 5]
?? ?fmt.Println("b= ", b) // [2 3]
?? ?fmt.Println("c= ", c) // [2 4]
?? ?fmt.Println("d= ", d) // [1 5]
?? ?/*
?? ??? ?第1处截取操作:a没有被修改
?? ??? ?第2处截取操作:a没有被修改
?? ??? ?第3处截取操作:会导致a被修改,并且a原本是1,2 其中2也没有了,截取的1和拼接的5组成了新的a
?? ?*/
}

?函数3:

 ?fmt.Println("cut97 start")
?? ?a := []int{1, 2}
?? ?b := append(a[0:1:1], 3)
?? ?//fmt.Println(a[1:2]) // [2]
?? ?fmt.Println("a= ", a) // [1 2]
?? ?fmt.Println("b= ", b) // [1 3]
?? ?c := append(a[1:2:2], 4)
?? ?fmt.Println("a= ", a) // [1 2]
?? ?fmt.Println("b= ", b) // [1 3]
?? ?fmt.Println("c= ", c) // [2 4]
?? ?/*
?? ??? ??? ?第1处截取操作:a没有被修改
?? ??? ??? ?第2处截取操作:a没有被修改
?? ?*/

函数4:

func cut98() {
?? ?fmt.Println("cut98 start")
?? ?a := []int{1, 2}
?? ?b := append(a[0:1:2], 3) // 这里如果改为a[0:1:1] a不会被修改
?? ?//fmt.Println(a[1:2]) // [2]
?? ?fmt.Println("a= ", a) // [1 3]
?? ?fmt.Println("b= ", b) // [1 3]
?? ?c := append(a[1:2], 4)
?? ?fmt.Println("a= ", a) // [1 3]
?? ?fmt.Println("b= ", b) // [1 3]
?? ?fmt.Println("c= ", c) // [3 4]
?? ?d := append(a[0:2], 5)
?? ?fmt.Println("a= ", a) // [1 3]
?? ?fmt.Println("b= ", b) // [1 3]
?? ?fmt.Println("c= ", c) // [3 4]
?? ?fmt.Println("d= ", d) // [1 3 5]
?? ?/*
?? ??? ??? ?第1处截取操作:a被修改
?? ??? ??? ?第2处截取操作:a没有被修改
?? ??? ??? ?第3处截取操作:a没有被修改
?? ?*/
}

现象不细说了,一看代码便知,这里汇总一下结论:

给原切片做截取操作同时套append时,原切片会不会被跟着被修改(注意这里讨论的是还有append的情况下,并不是单纯截取),按下面步骤。

首先看截取后的切片是否和原切片相等(如长度为2容量也为2),如果相等则当然不变,如:
a := []int{1, 2}
d := append(a[0:2], 5)
上面换成 ?d := append(a[0:2:2], 5) 或 ?d := append(a[0:], 5) 或 ?d := append(a[:], 5) 效果都和a本身一样
这样的情况原切片a必然不变。

如果不满足1的情况,则此时看截取时所得的切片长度和容量:

【下述的“所得切片”指的是截取后但未套append时的代称】

如果 所得切片的容量=原切片容量(注意没有套append时不牵扯扩容)
????a) 如果?数据长度<原切片数据长度(即仅截取了部分数据),则原切片会被修改;?
????如cut98()中的第一处截取append(a[0:1:2]、cut95()中的第一处截取append(a[0:1]
????但要注意的是,此时如果套了append并发生扩容时,即使满足此情况也不会修改原切片a,因为发生了扩容,原切片不能再满足,不会再做什么修改,cut95()中已说明。
?? ?
????b) 如果?数据长度=原切片数据长度(即截取了所有数据,此时视图是原切片全部),则原切片不会被修改; 和上面第一个情况相同;
????
如果 所得切片的容量<原切片容量
????a) 数据长度<原切片数据长度(即仅截取了部分数据),则原切片不会被修改;?
????如cut97()中的第2处截取append(a[1:2:2],或cut96()中的第1、2处截取append(a[1:2]
????
????b) 数据长度=原切片数据长度(即截取了所有数据),这种情况不存在,编译都不能通过:Invalid index values, must be low <= high <= max

关于截取时的初始索引是否从第一个位置开始的影响

如cut98()中的第一处截取append(a[0:1:2]会修改原切片a;(和append(a[0:1]一样),但如果改为a[0:1:1] a则不会被修改。
这两者都是从第一个位置开始截取,但结果显然不同,因此不能单以“截取时的初始索引是否从第一个位置开始”来定结果。

原切片会不会被修改,和截取时写成a[low:high]还是a[low:high:max]并无直接关系,和截取的初始索引是否从第一个位置开始也无直接关系。

修改原切片细节分析

那为什么所得切片的容量=原切片容量、且数据长度<原切片数据长度时,原切片就会被修改呢?

拿cut95的第一处来说:

?? ?a := []int{1, 2}
?? ?b := append(a[0:1], 3)

首先创建a切片,长度容量都是2,对应的底层数组是两个元素;

第二步分解,上面已经说明a[0:1]操作时不会修改a,a[0:1]相当于a[0:1:2],a[0:1]取得的值是1,对应的正是底层数组的第一个元素,a[0:1]这个切片本身地址和a一致,只是仅截取第一个元素;

清楚了上面后接着做append操作,在第一个元素位置的基础上,拼接了元素3,相当于3这个元素覆盖掉了第二个位置上的2这个元素,因此底层数组变成了1、3

那你有没有这样想过:1后面拼接了3,为什么不变为1,3,2(即2被挤压了)? ?而是1,3呢? 为什么把2没有挤到后面去而是覆盖操作?

如果2被挤压、下标变化,意味着切片a的容量发生变化,但实际上拼接没有发生在a上,拼接是给b做了拼接,既然没给a拼接,a本身数据长度就不应该发生变化。

相反如果做了b := append(a[0:1], 3, 4, 5, 6)操作后,情形是a原本的容量已不足支撑新数据,系统经过计算申请了新数组,新数组适配b,然后才会塞数据,而且一样拼接不是发生在a上,a切片就不会再动了,不会再影响原切片。

文章来源:https://blog.csdn.net/HYZX_9987/article/details/135151444
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。