如何理解go语言的slice

516 阅读3分钟

Go语言中的slice,通俗的来说就是动态的数组;我们都知道,数组的长度是固定的,一旦声明后,数组的长度是固定的;但是slice的长度是不固定的,声明后,仍然可以继续增加slice的长度,我们先看一下slice的数据结构是怎样的。

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

图片如上所示: 

  • array是数组的一个指针
  • len是当前切片的元素个数
  • cap表示当前切片的容量(即最大可以容纳多少个元素)

array指向的地方是一块连续的内存空间,存储切片的元素;连续的内存空间+长度+容量组成了切片,这里可以看成是数组的一个抽象,因为如果len和cap保持不变,slice本质就是一个数组;但slice和cap会发生变化,所以这就是切片。

切片的声明:

m := []int{1, 2, 3}
m := make([]int3)

如果我们要获取切片的一部分,可以这样

m := []int{1, 2, 3}
n0 := m[0:2//输出[1,2]
n1 := m[:2// 输出[1,2]

通过m[start:end]的方式获取切片的一部分,包含start不包含end,也可以不写start, m[:2]表示从开始到原切片下标为1的元素为止, 不写end,m[1:]表示从下标为1的元素开始到原切片元素结束为止。

我们下面再看一道有意思的代码,再理解下slice:

func main() {
    m := []int{1, 2, 3}  
    n := m[:2]  n = append(n, 4)  
    n = append(n, 5)  
    n = append(n, 6)  
    fmt.Printf("%v \n", m)  
    n[0] = 7  
    fmt.Printf("%v", m)
}

上面的代码会输出什么呢.一开始是不是会觉得第一个输出是:[1,2,4,5,6]
第二个输出是:[7,2,4,5,6]

正确答案肯定不是的,这里的正确输出两个都是:[1,2,4]

why?
我们一行行的来分析这段代码\

  • 首先:m := []int{1, 2, 3},已经限定了m的len是3, cap是3。
  • n := m[:2] ,n变成[1,2], m依旧是[1,2,3],n引用了m的一部分,n的len是2,cap是3。
  • n = append(n, 4), n变成[1,2,4],m变成了[1,2,4],因为n,m指向的同一块连续空间,n修改了第三个元素,m也会跟着变化。
  • n = append(n, 5),n的容量是3,这个时候发现容量不够了,需要扩容,扩容两倍,cap变成6,扩容后开辟了一个新的连续内存空间,此时,n与m已经没有关系了,所以不管后面n如何再append元素或者改变元素,都与m没有关系了。

那么两个输出都是[1,2,4]。

这里我们了解到slice的扩容机制:当cap不够的时候,如果slice的元素个数小于1024个扩大两倍;如果元素大于1024个,扩大1.25倍,并且每次扩容后会变成一个新的切片。

图片