go切片截取细节分析

 切片截取,有没有很迷?

目录

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

迷之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切片就不会再动了,不会再影响原切片。

相关推荐

  1. go切片截取细节分析

    2024-01-01 11:20:02       32 阅读
  2. go语言切片slice使用细节和注意事项整理

    2024-01-01 11:20:02       8 阅读
  3. Go-学会使用切片

    2024-01-01 11:20:02       15 阅读
  4. go切片删除

    2024-01-01 11:20:02       21 阅读
  5. Go语言切片

    2024-01-01 11:20:02       15 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-01 11:20:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-01 11:20:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-01 11:20:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-01 11:20:02       18 阅读

热门阅读

  1. 人机对话--关于意识机器

    2024-01-01 11:20:02       33 阅读
  2. 《微信小程序开发从入门到实战》学习六十八

    2024-01-01 11:20:02       37 阅读
  3. React16源码: Component与PureComponent源码实现

    2024-01-01 11:20:02       31 阅读
  4. Vue Router的介绍与引入

    2024-01-01 11:20:02       43 阅读
  5. 机器视觉系统选型-选型示例分享

    2024-01-01 11:20:02       39 阅读
  6. 2023年终总结及计划

    2024-01-01 11:20:02       34 阅读
  7. Python高级数据类型

    2024-01-01 11:20:02       40 阅读
  8. 《Linux Nano命令详解:小而强大的文本编辑器》

    2024-01-01 11:20:02       43 阅读
  9. vue3 element plus el-table封装(二)

    2024-01-01 11:20:02       40 阅读
  10. Vue-Setup

    Vue-Setup

    2024-01-01 11:20:02      32 阅读