从一道面试题看golang slice

之前遇到一道感觉很不错的 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

示意图

欢迎关注我们的微信公众号,每天学习Go知识

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章