1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1643963634997-bd2c5107-e40b-42d7-83d1-963d9f735f6a.png#clientId=ue84a5b62-8c59-4&from=paste&height=243&id=SsePB&margin=%5Bobject%20Object%5D&name=image.png&originHeight=322&originWidth=865&originalType=binary&ratio=1&size=92381&status=done&style=none&taskId=u8c8e2d82-ea6a-4e7f-9d2f-f30afa23749&width=652)

    参考资料:
    极客时间《go语言核心36讲》、《go语言中文文档》、《go语言圣经


    数组切片对比

    在golang语言中,数组类型的值(以下简称数组)的长度是固定的,而切片类型的值(以下简称切片)是可变长的。切片的底层实现是数组,可以将切片数据类型看作是将底层数组封装之后实现的动态数组。
    image.png


    go数据类型简介

    基础类型 数字、字符串和布尔型 统称为:值类型
    复合类型 数组、结构体
    引用类型 指针.、切片、字典、函数、通道、
    接口类型

    和java及python不同,Go 语言里不存在令人困惑的“传值或传引用”问题。如果传递的值是引用类型的,那么就是“传引用”。如果传递的值是值类型的,那么就是“传值”。


    数组

    Golang核心知识梳理|第一篇|数组和切片 - 图2
    数组赋值

    顺序初始化值序
    image.png
    指定一个索引和对应值列表的方式初始化
    image.png

    数组大小固定

    1. var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
    2. 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
    3. 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值

    因为数组的这些特性,在进行参数传递时,或者需要动态数组时,应使用slice


    slice切片

    1. slice 是对数组的封装数据结构(包括元素:指针、长度、容量)
    2. slice的第一个元素并不一定就是底层数组的第一个元素
    3. 多个slice之间可以共享底层的数组
    4. slice的切片操作s[i:j],用于创建一个新的slice

    slice并不是一个纯粹的引用类型,它实际上是一个类似下面结构体的聚合类型

    1. type IntSlice struct {
    2. ptr *int //底层数据
    3. len, cap int //长度、容量
    4. }

    示例:

    1. Q2 := months[4:7]
    2. summer := months[6:9]
    3. fmt.Println(Q2) // ["April" "May" "June"]
    4. fmt.Println(summer) // ["June" "July" "August"]
    1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1643956123710-5dc3d9da-d071-4b20-a21f-4e23bc7f5ec7.png#clientId=u2b2bb302-f915-4&from=paste&height=480&id=ucc380440&margin=%5Bobject%20Object%5D&name=image.png&originHeight=640&originWidth=750&originalType=binary&ratio=1&size=85795&status=done&style=none&taskId=u4a268e02-0735-4497-a5fa-433e11f4b36&width=563)<br />注意两个基于同一底层数组的两个切片Q2及Q1的cap值

    用make函数创建slice

    1. make([]T, len)
    2. make([]T, len, cap) // same as make([]T, cap)[:len]
    3. //cap,可以省略,省略时cap==len

    示例代码,创建切片,并返回切点的容量(cap)和长度(lenght)

    1. package main
    2. import "fmt"
    3. func main() {
    4. // 示例1。
    5. s1 := make([]int, 5)
    6. fmt.Printf("The length of s1: %d\n", len(s1))
    7. fmt.Printf("The capacity of s1: %d\n", cap(s1))
    8. fmt.Printf("The value of s1: %d\n", s1)
    9. s2 := make([]int, 5, 8)
    10. fmt.Printf("The length of s2: %d\n", len(s2))
    11. fmt.Printf("The capacity of s2: %d\n", cap(s2))
    12. fmt.Printf("The value of s2: %d\n", s2)
    13. }

    动态扩容的过程

    实现一个slice动态扩容函数、实际中的扩容策略是更复杂的

    1. /append底层实现
    2. //是一个动态扩容的过程
    3. func appendInt(x []int, y int) []int {
    4. var z []int
    5. zlen := len(x) + 1
    6. if zlen <= cap(x) {
    7. // There is room to grow. Extend the slice.
    8. z = x[:zlen]
    9. } else {
    10. // There is insufficient space. Allocate a new array.
    11. //cap扩容为len的两倍
    12. zcap := zlen
    13. if zcap < 2*len(x) {
    14. zcap = 2 * len(x)
    15. }
    16. z = make([]int, zlen, zcap)
    17. copy(z, x) // a built-in function; see text
    18. }
    19. z[len(x)] = y
    20. return z
    21. func main() {
    22. var x, y []int
    23. for i := 0; i < 10; i++ {
    24. y = appendInt(x, i)
    25. fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
    26. x = y
    27. }
    28. }
    29. 动态扩容过程:
    30. 0 cap=1 [0]
    31. 1 cap=2 [0 1]
    32. 2 cap=4 [0 1 2]
    33. 3 cap=4 [0 1 2 3]
    34. 4 cap=8 [0 1 2 3 4]
    35. 5 cap=8 [0 1 2 3 4 5]
    36. 6 cap=8 [0 1 2 3 4 5 6]
    37. 7 cap=8 [0 1 2 3 4 5 6 7]
    38. 8 cap=16 [0 1 2 3 4 5 6 7 8]
    39. 9 cap=16 [0 1 2 3 4 5 6 7 8 9]
    1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1643968159358-073ea407-0215-4b7b-9fa2-0cb79f6c6229.png#clientId=ue84a5b62-8c59-4&from=paste&height=310&id=u6bdbe227&margin=%5Bobject%20Object%5D&name=image.png&originHeight=310&originWidth=838&originalType=binary&ratio=1&size=51623&status=done&style=none&taskId=u0b03b545-266d-4719-b9ce-0e88ac6e127&width=838)

    `通过我们的扩容伪代码,会发现,其实这里并不能算是真正的在底层数据上进行了库容,而是创建了一个基于新底层数组的新切片,原来的底层数组和切片是没有改变的,所以如果使用append()函数在切片中插入数据时还需要对原切片重新赋值。

    测试代码:
    image.png