一篇文章理解 golang 中切片与数组的关系

enda · · 543 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

起始

在 golang 文档中,对数组与切片有一些详细的讲解,本文主要讲解数组与切片的关系

由于是个人理解,可能有些偏差,烦请指正

数组

golang 的数组比较简单,我们理解几个概念即可

  1. 数组是固定长度与容量,并且具有相同类型的一组值
  2. 此定义的数组长度为 5 ,那么容量也会固定为 5
  3. 数组的索引都是从 0 开始的

记住,我们在此定义了一个 int 类型的数组,长度容量均为 5,在后面的切片讲解中,我们将对此数组进行切片

//  此定义的数组长度为 5 ,那么容量也会固定为 5
arr := [5]int{0, 1, 2, 3, 4}
//  数组 p = 0xc00001c0f0,arr = [0 1 2 3 4],len = 5,cap = 5
fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
// 数组的索引都是从 0  开始的
// 0 1
fmt.Println(arr[0], arr[1])

切片

切片的基础概念

  1. 切片可以看做是一个可变长的数组
  2. 切片可以看做是对数组的一个片段的引用

创建切片

我们首先来创建一个对 arr 数组的一个切片

[1:3] 表示范围从数组的索引 1 至 数组的索引 3,它是不包括 3 的,可以简单理解为 index == 1 && index < 3
当切片生成后,索引默认也是从 0 开始, 切片索引是 [0,1] 对应着数组的索引是 [1,2]

arrSlice := arr[1:3]
// 切片 p = 0xc00000c060,arr = [1 2],len = 2,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

修改数组,切片会发生什么?

我们上面有提到过,切片是对数组的一个引用,那么我们修改数组的值,切片会发生什么呢?

// 把数组 index = 1 的值修改为 10
arr[1] = 10

// 切片 p = 0xc00000c060,arr = [10 2],len = 2,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice)

可以看到,我们切片对应的值也被修改为 10,切片的底层数据结构中存在的底层数组是对我们上面数组做的引用传递,关于引用传递和值传递大家应该还是分的清的

那么对应的,我们修改切片,数组也会发生相应的变化

// 同样的道理,修改切片的值也会影响到底层数组
arrSlice[0] = 8
// 切片 p = 0xc00000c060,arr = [8 2],len = 2,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 数组 p = 0xc00001c0f0,arr = [0 8 2 3 4],len = 5,cap = 5
fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

对切片进行追加操作,会发生什么?

上面说到,我们修改切片或者是数组,都会有对应的变化,那么我们对切片进行追加呢?

// 对切片追加
arrSlice = append(arrSlice, 11)
// 切片 p = 0xc00008a020,arr = [8 2 11],len = 3,cap = 4
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 数组 p = 0xc000090060,arr = [0 8 2 11 4],len = 5,cap = 5
fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

我们可以看到,数组的 index = 3 的值被修改为 11,我是这么理解的:

  1. 追加前的切片索引是 [0,1] 对应着数组的 [1,2]
  2. 在这里追加了一位,那么切片的索引是 [0,1,2] 数组对应的索引是 [1,2,3]
  3. 所以追加的值,也会修改数组的值

切片的容量?

通过上面的代码,我们可以看到我们的 cap 一直都是 4,为什么呢?这就要涉及到一个容量的计算公式了

  1. 在创建切片时,我们指定了对数组的 [1,3] 进行切片,这里切出来的长度为 2
  2. 切片在创建时,如果不指定容量,那么容量会自动去计算,我们这里没有指定容量,那么他会自动计算
  3. 创建时计算容量的公式为:创建时的长度 * 2,我们的长度是 2 ,计算出来的容量则为 4
  4. 在追加时,会先判断容量够不够,如果容量足够则容量不变;如果超出容量那么判断长度是否小于 1024 ,小于则容量 * 2,大于则容量 * 1.25

这就是为什么我们的容量一直是 4 的原因

切片容量超过原数组容量,会发生什么?

我们一直在反复提,切片是对数组的引用,那么当我切片已经超出数组的范围,会发生什么呢?

我们写代码操作一下

arrSlice = append(arrSlice, 12, 13, 14)
// 切片 p = 0xc00000c060,arr = [8 2 11 12 13 14],len = 6,cap = 8
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 数组 p = 0xc00001c0f0,arr = [0 8 2 11 4],len = 5,cap = 5
fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

在上面代码中,我们追加了三个值 12,13,14

我们可以看到,数组的值并没有被修改,按照我们上一步讲的来说,应该数组的最后一位会变为 12,但是并没有
这是一个很重要的概念,当我们的切片容量大于底层数组容量时,会自动创建一个新的底层数组,取消对原数组的引用

当我们切片取消对原数组引用时,我们再去修改切片,并不会影响到原数组

arrSlice[0] = 18
// 切片 p = 0xc00008c020,arr = [18 2 11 12 13 14],len = 6,cap = 8
fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
// 数组 p = 0xc000092060,arr = [0 8 2 11 4],len = 5,cap = 5
fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

被取消引用的数组去哪了?

根据查阅资料来看,大部分人都是说当被取消引用,并且也没有别的地方去引用时,golang 的垃圾处理会自动回收;大家可以具体尝试一下

完整代码

此文章完整代码如下

// Enda <endachao@gmail.com>
package main

import "fmt"

func main() {
    //数组是固定长度与容量,并且具有相同类型的一组值
    //此定义的数组长度为 5 ,那么容量也会固定为 5
    arr := [5]int{0, 1, 2, 3, 4}
    // 数组 p = 0xc00001c0f0,arr = [0 1 2 3 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))
    // 数组的索引都是从 0  开始的
    // 0 1
    fmt.Println(arr[0], arr[1])

    // 切片可以看做是一个可变长的数组
    // 切片可以看做是对数组的一个片段的引用
    // [1:3] 表示范围从数组的索引 1 至 数组的索引 3,它是不包括 3 的,可以简单理解为  index == 1 && index < 3
    // 当切片生成后,索引默认也是从 0 开始, 切片索引是 [0,1] 对应着数组的索引是 [1,2]
    arrSlice := arr[1:3]
    // 切片 p = 0xc00000c060,arr = [1 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

    // 由于切片是对底层数据的一个引用,所以修改底层数组会更改切片的值
    arr[1] = 10
    // 切片 p = 0xc00000c060,arr = [10 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))

    // 同样的道理,修改切片的值也会影响到底层数组
    arrSlice[0] = 8
    // 切片 p = 0xc00000c060,arr = [8 2],len = 2,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc00001c0f0,arr = [0 8 2 3 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    // 对切片追加
    // 追加前的切片索引是 [0,1] 对应着 数组的 [1,2]
    // 在这里追加了一位,那么切片的索引是 [0,1,2] 数组对应的索引是 [1,2,3]
    // 所以追加的值,也会修改数组的值
    arrSlice = append(arrSlice, 11)
    // 切片 p = 0xc00008a020,arr = [8 2 11],len = 3,cap = 4
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc000090060,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    // 切片的容量为什么是 4
    // 在创建切片时,我们指定了对数组的 [1,3] 进行切片,这里切出来的长度为 2
    // 切片在创建时,如果不指定容量,那么容量会自动去计算
    // 创建时计算容量的公式为:创建时的长度 * 2
    // 追加时,会先判断长度够不够,如果够则容量不变,如果不够那么判断长度是否小于 1024 ,小于则容量 * 2,大于则 容量 * 1.25

    // 当我们切片的长度超过了原数组的长度
    // 我们可以看到,数组的值并没有被修改,按照我们上一步讲的来说,应该数组的最后一位会变为 12,但是并没有
    // 这是一个很重要的概念,当我们的切片容量大于底层数组容量时,会自动创建一个新的底层数组
    arrSlice = append(arrSlice, 12, 13, 14)
    // 切片 p = 0xc00000c060,arr = [8 2 11 12 13 14],len = 6,cap = 8
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc00001c0f0,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

    //当我再去修改切片的值时,并不会去操作我们上面定义的数组了
    arrSlice[0] = 18
    // 切片 p = 0xc00008c020,arr = [18 2 11 12 13 14],len = 6,cap = 8
    fmt.Printf("切片 p = %p,arr = %+v,len = %d,cap = %d\n", &arrSlice, arrSlice, len(arrSlice), cap(arrSlice))
    // 数组 p = 0xc000092060,arr = [0 8 2 11 4],len = 5,cap = 5
    fmt.Printf("数组 p = %p,arr = %+v,len = %d,cap = %d\n", &arr, arr, len(arr), cap(arr))

}

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:enda

查看原文:一篇文章理解 golang 中切片与数组的关系

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

543 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传