美文网首页
从一道面试题看golang slice

从一道面试题看golang slice

作者: airun | 来源:发表于2020-05-29 16:23 被阅读0次
    之前遇到一道感觉很不错的slice面试题,这里分享出来,先不贴答案了大家可以先思考下每个地方的打印会是什么;最后再给大家公布答案
        v := make([]int, 0, 5)
        v = append(v, 2, 3, 5)
        a := append(v, 0, -1)
        fmt.Println(v) 
        fmt.Println(a) 
    
        b := append(v, 1)
        fmt.Println()
        fmt.Println(v) 
        fmt.Println(a) 
        fmt.Println(b) 
    
        c := append(v, 6, 7, 8, 9)
        fmt.Println()
        fmt.Println(v) 
        fmt.Println(a) 
        fmt.Println(b) 
        fmt.Println(c) 
    
        d := append(v, 12)
        fmt.Println(d) 
        fmt.Println(v) 
        fmt.Println(a) 
        fmt.Println(b) 
        fmt.Println(c) 
        fmt.Println(d) 
    
    在解这道面试题之前先来看下slice常用用法

    一般通过make([]T,len,cap)来创建slice
    其中cap可以省略则跟len的值相同
    len 表示存储的元素个数,cap表示容量

    slice 初始化姿势
        // 1、初始化时添加好了数据,这时候len=cap=初始化的数据个数
        s1 := []int{1, 2, 3, 4, 5, 6}
        fmt.Println(s1, len(s1), cap(s1)) // [1 2 3 4 5 6] 6 6
    
        // 2、只申明 len,cap跟len相同
        s2 := make([]int, 5)
        fmt.Println(s2, len(s2), cap(s2)) // [0 0 0 0 0] 5 5
    
        // 3、同时申明len,cap
        s3 := make([]int, 5, 5)
        fmt.Println(s3, len(s3), cap(s3)) // [0 0 0 0 0] 5 5
    
        // 4、先声明一个数组,从数组处申明slice
        arr := [5]int{1, 2, 3, 4, 5}
        s4 := arr[:]
        fmt.Println(arr)                  // [1 2 3 4 5]
        fmt.Println(s4, len(s4), cap(s4)) // [1 2 3 4 5] 5 5
    
    slice 的len很容易理解,就是slice元素个数;那cap有什么用呢?下面通过例子来看下
        s1 := make([]int, 5, 6)
        fmt.Println(s1, len(s1), cap(s1))
        fmt.Printf("%p\n", s1)
    
        s1 = append(s1, 1)
        fmt.Println(s1, len(s1), cap(s1))
        fmt.Printf("%p\n", s1)
    
        s1 = append(s1, 2)
        fmt.Println(s1, len(s1), cap(s1))
        fmt.Printf("%p\n", s1)
    
        s1 = append(s1, 3)
        fmt.Println(s1, len(s1), cap(s1))
        fmt.Printf("%p\n", s1)
    =====================================================
    [0 0 0 0 0] 5 6
    0xc000216000
    [0 0 0 0 0 1] 6 6
    0xc000216000
    [0 0 0 0 0 1 2] 7 12
    0xc000030660
    [0 0 0 0 0 1 2 3] 8 12
    0xc000030660
    
    从上面的结果可以看出来,slice cap是包含len的,也就是说len是cap的一部分,而cap-len的部分是待append数据时存放数据的部分,在打印slice时也是不展示的;slice在append数据时如果cap-len还有空间则会将数据添加到这部分空间中,如果cap-len已经为0,则slice需要扩充整个slice的cap,扩充的元素个数就是当前slice的cap大小。

    就如上面的例子中在append 1时 cap还有剩余空间可以放数据所以添加之后 s1的地址没变;在append 2时 cap已经为0了所以这时候需要对s1做扩容操作;扩容的数据量就是初始化s1时设置的cap大小6,然后将之前的数据重新copy的新的slice中,所以可以看到扩容之后slice的地址也发生了变化;扩容之后的cap大小为12;所以在初始化slice时给个合理的cap是非常重要的;因为slice在扩容时是按照当前cap的大小成倍增长的

    slice 的本质

    slice在真正做存储时其实是对应着一个数组;而slice只是这个数组的视图而已;slice的len就是slice这个视图能够看到这个底层数组的窗口大小,而cap是这个底层数组的大小

    假如初始化一个 s := make([]int,8,13);这时候s跟底层数组如下图所示

    slice存储

    如上图所示不管用那种方式初始化slice,最终在底层都是通过一个数组来存储数据,而slice只是这个底层数组的一个视图,而len就是这个视图的窗口大小;这个类似于数据库中的View的概念;有了这个基础之后我来再回头看文章开头说的面试题就很好做了

    我们先把文章开头的面试题以及答案贴出来,下面再通过图示的方式来解答
        // v底层数组值 2,3,5,0,-1
        v := make([]int, 0, 5)
        v = append(v, 2, 3, 5)
        a := append(v, 0, -1)
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,0,-1
    
        // v底层数组值 2,3,5,1,-1
        b := append(v, 1)
        fmt.Println()
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,1,-1
        fmt.Println(b) // 2,3,5,1
    
        // v底层数组值 2,3,5,1,-1,6,7,8,9
        c := append(v, 6, 7, 8, 9)
        fmt.Println()
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,1,-1
        fmt.Println(b) // 2,3,5,1
        fmt.Println(c) // 2,3,5,6,7,8,9
    
        // v底层数组值 2,3,5,12,-1
        d := append(v, 12)
        fmt.Println()
        fmt.Println(d) // 2,3,5,12
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,12,-1
        fmt.Println(b) // 2,3,5,12
        fmt.Println(c) // 2,3,5,1,6,7,8,9
        fmt.Println(d) // 2,3,5,12
    
    1、第一层打印
        v := make([]int, 0, 5)
        v = append(v, 2, 3, 5)
        a := append(v, 0, -1)
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,0,-1
    
    图示底层数组变化过程
    2、第二层打印
        b := append(v, 1)
        fmt.Println()
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,1,-1
        fmt.Println(b) // 2,3,5,1
    
    示意图
    3、第三层打印
    // v底层数组值 2,3,5,1,-1,6,7,8,9
        c := append(v, 6, 7, 8, 9)
        fmt.Println()
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,1,-1
        fmt.Println(b) // 2,3,5,1
        fmt.Println(c) // 2,3,5,6,7,8,9
    
    示意图
    4、第四层打印
        d := append(v, 12)
        fmt.Println()
        fmt.Println(d) // 2,3,5,12
        fmt.Println(v) // 2,3,5
        fmt.Println(a) // 2,3,5,12,-1
        fmt.Println(b) // 2,3,5,12
        fmt.Println(c) // 2,3,5,1,6,7,8,9
        fmt.Println(d) // 2,3,5,12
    
    示意图

    相关文章

      网友评论

          本文标题:从一道面试题看golang slice

          本文链接:https://www.haomeiwen.com/subject/hjjxzhtx.html