Golang语言系列-05-数组和切片
阅读原文时间:2023年07月08日阅读:3

数组和切片

概念

数组是同一种数据类型元素的集合;数组的长度必须是常量,并且长度是数组类型的一部分,一旦定义,长度不能变

例如:[5]int 和 [10]int 是不同的数组类型

使用时可以修改数组成员,但是数组大小长度不可变化

数组的初始化

package main

import (
    "fmt"
)

func main() {
    var a1 [3]bool
    var a2 [4]bool
    var a122 [3]func()
    fmt.Printf("a1:%T a2:%T a122:%T\n", a1, a2, a122) //a1:[3]bool a2:[4]bool a122:[3]func()

    // 数组的初始化
    // 如果不初始化:默认元素都是零值(布尔值:false,整型和浮点型都是0,字符串为空:"",函数为nil)
        // a1:[false false false] a2:[false false false false] a122:[<nil> <nil> <nil>]
    fmt.Printf("a1:%v a2:%v a122:%v\n", a1, a2, a122) 

    // 初始化的方式
    // 1.初始化方式1
    a1 = [3]bool{true, true, true}
    fmt.Println(a1) //[true true true]

    // 2.初始化方式2:根据初始值自动推断数组的长度是多少
    a10 := [...]int{1, 2, 3, 4, 5, 6, 7, 8}
    fmt.Println(a10) //[1 2 3 4 5 6 7 8]

    // 3.初始化方式3:根据索引来初始化
    // a3 := [...]int{0:1, 4:2}  //索引为0的值为1,索引为4的值为2
    a3 := [5]int{0: 1, 4: 2} //索引为0的值为1,索引为4的值为2
    fmt.Println(a3)          //[1 0 0 0 2]
}

数组的遍历

package main

import (
    "fmt"
)

func main() {
        // 数组的遍历
    citys := [...]string{"北京", "上海", "深圳"}
    // 1.根据索引遍历
    // 用 len() 函数统计数组的长度然后在进行 for 循环是可以的。这里和string类型的遍历有差别
    for i := 0; i < len(citys); i++ {
        fmt.Println(citys[i])
    }
    // 2.for range遍历
    for _, v := range citys {
        fmt.Println(v)
    }
}

多维数组

package main

import (
    "fmt"
)

func main() {
        // 多维数组
        // 注意:多维数组只有第一层可以使用 [...]int{1, 2, 3} 来让编译器推导数组长度
    // [ [1 2] [3 4] [5 6] ]
    var a11 [3][2]int
    a11 = [3][2]int{
        [2]int{1, 2},
        [2]int{3, 4},
        [2]int{5, 6},
    }
    fmt.Println(a11) //[[1 2] [3 4] [5 6]]
    a12 := [3][2]int{
        [2]int{1, 2},
        [2]int{3, 4},
        [2]int{5, 6},
    }
    fmt.Println(a12) //[[1 2] [3 4] [5 6]]

    // 多维数组遍历
    // 1 2 3 4 5 6
    for _, v1 := range a11 {
        for _, v2 := range v1 {
            fmt.Println(v2)
        }
    }
}

数组是值类型

package main

import (
    "fmt"
)

func main() {
        // 数组是值类型
    // 赋值和传参会复制整个数组,因此改变副本的值,不会改变原来数组本身的值
    fmt.Println("------ 数组是值类型 ------")
    b1 := [3]int{1, 2, 3} //[1 2 3]
    b2 := b1              //[1 2 3] Ctrl+C Ctrl+V 相当于把world文档从文件夹A拷贝到文件夹B
    b2[0] = 100           //b2:[100 2 3]
    //b1[0] = 2 //数组可以修改值,但是长度不能变
    fmt.Println(b1, b2) //b1:[1 2 3]  b2:[100 2 3]

    // 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的
    arr1 := [...]int{1, 2, 3}
    arr2 := [...]int{1, 2, 3}
    fmt.Println(arr1 == arr2) //true
}

指针数组和数组的指针

package main

import (
    "fmt"
)

func main() {
        // 指针数组
    // [n]*T表示指针数组
    s1 := "abc"
    s2 := &s1
    s3 := &s1
    arr3 := [...]*string{s2, s3}
    fmt.Println(arr3) //[0xc00008e240 0xc00008e240]

    // 数组的指针
        // *[n]T表示数组的指针
    arr4 := [...]int{1, 2}
    op := &arr4
    fmt.Println(*op)          //[1 2]
    fmt.Printf("%T\n", *op)   //[2]int
    fmt.Printf("%p\n", op)    //0xc0000180e0  op的值是数组arr4这个变量的指针
    fmt.Printf("%p\n", &op)   //0xc00000e030  op自己在内存中的地址
    fmt.Printf("%p\n", &arr4) //0xc0000180e0  op的值是数组arr4这个变量的指针
}

数组练习

package main

import "fmt"

// array数组练习题
// 1.求数组[1, 3, 5, 7, 8]所有元素的和
// 2.找出数组中和为指定值的两个元素的下标,比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)和(1,2)

func main() {
    // 1
    a1 := [...]int{1, 3, 5, 7, 8}
    sum1 := 0
    for _, v := range a1 {
        sum1 += v
    }
    fmt.Println("sum1:", sum1)

    // 2
    a2 := [...]int{1, 3, 5, 7, 8}
    for i := 0; i < len(a2); i++ {
        for j := i + 1; j < len(a2); j++ {
            if a2[i]+a2[j] == 8 {
                fmt.Printf("(%d,%d)\n", i, j)
            }
        }
    }
}

概念

切片是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容

切片是一个引用类型(数组是一个值类型),它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合

内置的len()函数求切片长度,内置的cap()函数求切片的容量

切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片

判断切片是否为空 请始终使用len(s) == 0来判断,而不应该使用s == nil来判断 初始化以后的切片 != nil

package main

import (
    "fmt"
)

func main() {
    // 切片的定义
    var s1 []int                  //定义一个存放int类型元素的切片
    var s2 []string               //定义一个存放string类型元素的切片
    fmt.Println(s1, s2)           //[] []
    fmt.Println(s1 == nil)        //true
    fmt.Println(s2 == nil)        //true
    fmt.Println(len(s1), len(s2)) // 0 0

        // 切片初始化
    s1 = []int{1, 2, 3}
    s2 = []string{"沙河", "张江", "平山村"}
    fmt.Println(s1, s2)           //[1 2 3] [沙河 张江 平山村]
    fmt.Println(s1 == nil)        //false
    fmt.Println(s2 == nil)        //false
    fmt.Println(len(s1), len(s2)) // 3 3

        // 长度和容量
    fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1), cap(s1)) //len(s1):3 cap(s1):3
    fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2), cap(s2)) //len(s2):3 cap(s2):3

        // 由数组得到切片
    a1 := [...]int{1, 3, 5, 7, 9, 11, 13} //数组
    s3 := a1[0:4]                         //基于一个数组切割,顾首不顾尾
    fmt.Println(s3)                       //[1 3 5 7]
    s4 := a1[1:6]
    fmt.Println(s4) //[3 5 7 9 11]
    s5 := a1[:4]
    s6 := a1[3:]
    s7 := a1[:]
    fmt.Println(s5) //[1 3 5 7]
    fmt.Println(s6) //[7 9 11 13]
    fmt.Println(s7) //[1 3 5 7 9 11 13]
        // 切片的容量:是指底层数组的容量
    fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) // len(s5):4 cap(s5):7
    // 切片容量:底层数组从切片的第一个元素到最后的元素数量
    fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) // len(s6):4 cap(s6):4
    // 切片再切割
    s8 := s6[3:]                                            //[13]
    fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8)) //len(s8):1 cap(s8):1

        // 切片是引用类型,都指向了底层的一个数组
    // 修改底层数组的值,会影响切片
    fmt.Println("s6: ", s6) //[7 9 11 13]
    a1[6] = 1300            //修改底层数组的值
    fmt.Println("s6: ", s6) //[7 9 11 1300]
    fmt.Println("s8: ", s8) //[1300]
    fmt.Println("a1: ", a1) //[1 3 5 7 9 11 1300]

        // 修改切片,会修改底层数组吗?如果只是修改值的话不涉及到扩容,是会修改原底层数组的
    s8[0] = 10000
    fmt.Println(s8)           //[10000]
    fmt.Println(a1)           //[1 3 5 7 9 11 10000] a1数组发生改变
    s8 = append(s8, 20000)    //此处已经产生了新的数组,切片s8的容量是1,s8的切片不再指向原来的底层数组
    fmt.Printf("s8:%v\n", s8) //[10000 20000]
    fmt.Println(a1)           //[1 3 5 7 9 11 10000] a1数组不发生改变

        // append追加数组元素,如果底层数组容量不够的时候会扩容,产生新的数组
    a10 := [5]int{1, 2, 3}
    s10 := a10[:]
    fmt.Printf("len(s10):%d cap(s10):%d\n", len(s10), cap(s10)) //len(s10):5 cap(s10):5
    //s10 = append(s10, 4)
    //fmt.Println(cap(s10)) //10
    s10[3] = 1
    //fmt.Println(a10, s10) //[1 2 3 0 0] [1 2 3 1 0 4] 如果没注释s10的append,证明Go已经把底层的数组换了
    fmt.Println(a10, s10) //[1 2 3 1 0] [1 2 3 1 0] 如果注释了s10的append,则s10[3]=1修改的是底层元素
}

make函数初始化切片

package main

import (
    "fmt"
)

func main() {
        // make函数初始化切片
    var a = make([]string, 5, 10) // make(类型,长度,容量)
    fmt.Printf("cap:%d %#v\n", cap(a), a) //cap:10 []string{"", "", "", "", ""}

        // append()
        for i := 0; i < 10; i++ {
        a = append(a, fmt.Sprintf("%v", i)) // 拼接成字符串
    }
    fmt.Printf("%#v\n", a)
        //[]string{"", "", "", "", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}

    fmt.Println(len(a), cap(a)) //15 20

        // 初始化
    s1 := make([]int, 5, 10)                                          //make(类型,长度,容量)
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) //s1=[0 0 0 0 0] len(s1)=5 cap(s1)=10

    s2 := make([]int, 0, 10)
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s2, len(s2), cap(s2)) //s1=[] len(s1)=0 cap(s1)=10

        // 切片的赋值
    s3 := []int{1, 3, 5}
    s4 := s3            //s3和s4都指向了同一个底层数组
    fmt.Println(s3, s4) //[1 3 5] [1 3 5]
    s3[0] = 1000
    fmt.Println(s3, s4) //[1000 3 5] [1000 3 5]
}

切片的遍历

package main

import (
    "fmt"
)

func main() {
        // 切片的遍历
        s3 := []int{1, 3, 5}

    // 1.索引遍历
    for i := 0; i < len(s3); i++ {
        fmt.Println(s3[i])
    }

    // 2.for range循环
    for _, v := range s3 {
        fmt.Println(v)
    }
}

切片的append()

package main

import (
    "fmt"
)

// append()为切片追加元素
// 可能会导致数组扩容,从而让切片指向新的数组

func main() {
    s1 := []string{"北京", "上海", "深圳"}
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) //s1=[北京 上海 深圳] len(s1)=3 cap(s1)=3
    //s1[3] = "guangzhou" //panic: runtime error: index out of range [3] with length 3

    // 调用append函数必须用原来的切片变量接收返回值
    // append追加元素,原来的底层数组放不下的时候,Go就会把底层数组换一个
    // 必须用变量接收append的返回值
    s1 = append(s1, "广州")
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
        //s1=[北京 上海 深圳 广州] len(s1)=4 cap(s1)=6

    s1 = append(s1, "杭州", "成都")
    fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
        //s1=[北京 上海 深圳 广州 杭州 成都] len(s1)=6 cap(s1)=6

    ss := []string{"武汉", "西安", "苏州"}
    s1 = append(s1, ss...)
        //表示拆开(打散),一个一个的元素添加到别的切片

        fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
        //s1=[北京 上海 深圳 广州 杭州 成都 武汉 西安 苏州] len(s1)=9 cap(s1)=12
}

切片的拷贝

package main

import (
    "fmt"
)

// 切片的copy

func main() {
    a1 := []int{1, 3, 5}
    aa := a1[:1]
    fmt.Println(cap(aa), len(aa), aa) // 容量是3 长度是1 [1]
    a2 := a1                          //赋值 a2 a1 指向的是同一个底层数组
    var a3 = make([]int, 3, 3)
    copy(a3, a1)            //copy 底层数组复制了一份,a3 和 a1 指向的不是同一个底层数组
    fmt.Println(a1, a2, a3) //[1 3 5] [1 3 5] [1 3 5]
    a1[0] = 100
    fmt.Println(a1, a2, a3) //[100 3 5] [100 3 5] [1 3 5]

    // 将a1中的索引为1的 3 这个元素删掉。 ... 由于切片没有直接删除元素的方法,所以可以采用这种方法
    a1 = append(a1[:1], a1[2:]...) // a1[:1] 容量是3,append 1 个元素的时候,底层数组没有发生变化
    fmt.Println(a1)                // [100 5]
    fmt.Println(cap(a1))           // 3
    fmt.Println(a2)                // [100 5 5]

    x1 := [...]int{1, 3, 5} //数组
    fmt.Println("===", x1)
    s1 := x1[:] //数组经过[L:M]以后,可以得到切片
    fmt.Println(s1, len(s1), cap(s1))
    // 1.切片不保存具体的值
    // 2.切片对应一个底层数组
    // 3.底层数组都是占用一块连续的内存
    fmt.Printf("%p\n", &s1[0])
    s1 = append(s1[:1], s1[2:]...)    //s1[:1] 容量是3,append 1 个元素的时候,底层数组没有发生变化
    fmt.Printf("%p\n", &s1[0])        //Go语言中不存在指针操作,只需要记住两个符号:&:取地址 *:根据地址取值
    fmt.Println(s1, len(s1), cap(s1)) //[1 5] 2 3
    fmt.Println(x1)                   //[1 5 5]
    s1[0] = 100
    fmt.Println(x1) //[100 5 5]
}

切片练习

package main

import (
    "fmt"
    "sort"
)

// 切片练习题

func main() {
    var a = make([]int, 5, 10) //初始化切片,长度5,容量10
    fmt.Println(a)             //[0 0 0 0 0]
    for i := 0; i < 10; i++ {
        a = append(a, i)
    }
    fmt.Println(a)      //[0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
    fmt.Println(cap(a)) //20

    var a1 = [...]int{3, 7, 8, 9, 1}
    sort.Ints(a1[:]) //对切片进行排序
    fmt.Println(a1)  //[1 3 7 8 9]

    // 要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断
    var aa []int           //只定义,没初始化
    fmt.Println(aa == nil) //true
    var bb []int           //定义
    bb = []int{}           //初始化后就不为nil了,但是还是空的切片
    fmt.Println(bb == nil) //false
    fmt.Println(aa, bb)    //[] []
    if len(bb) == 0 {
        fmt.Println("空数组")
    }
}



package main

import "fmt"

//切片练习

func main() {
    //ex1
    //s1 := make([]int, 4, 100)
    //s1[0] = 1
    //s1[1] = 2
    //fmt.Println(s1) //[1 2 0 0]
    //s2 := s1[:]     //再切片,切片的切片,底层数组还是同一个
    ////s2 := s1 //赋值 底层数组是同一个
    //s2[0] = 100
    //fmt.Println(s1)               //[100 2 0 0]
    //fmt.Println(s2)               //[100 2 0 0]
    //fmt.Println(cap(s1), cap(s2)) //100 100

    //ex2
    //s1 := make([]int, 4, 4)
    //s1[0] = 1
    //s1[1] = 2
    //fmt.Println(s1) //[1 2 0 0]
    //s2 := s1[:]     //切片的再切片 len:4 cap:4
    //
    ////append()扩容以后会形成新的数组
    //s2 = append(s2, 100) //形成新的底层数组 [1 2 0 0 100] len:4 cap:8
    //
    //fmt.Println(s1)               //[1 2 0 0]
    //fmt.Println(s2)               //[1 2 0 0 100]
    //fmt.Println(cap(s1), cap(s2)) //4 8

    //ex3
    //s1 := make([]int, 4, 4)
    //s1[0] = 1
    //s1[1] = 2
    //fmt.Println(s1)                         //[1 2 0 0]
    //s2 := s1[:2]                            //[1 2] len:2 cap:4
    //fmt.Println("==", s2, len(s2), cap(s2)) //[1 2] 2 4
    //s2 = append(s2, 100)                    //append()这里没有扩容,因为长度是2,容量是4,所以不会形成新的底层数组,此时底层数组是:[1 2 100]
    //fmt.Println(s1)                         //[1 2 100 0]
    //fmt.Println(s2)                         //[1 2 100] 这里s2的长度是3,不是4哦,所以是 [1 2 100]
    //fmt.Println(cap(s1), cap(s2))           //4 4
    //fmt.Println(len(s1), len(s2))           //4 3

    //ex4
    //var s1 = [...]int{1, 2, 3, 4, 5}
    //s2 := s1[2:] //[3 4 5] len:3 cap:3
    //
    //s2 = append(s2, 100) //扩容了,产生新的底层数组 [3 4 5 100] len:4 cap:6
    //
    //fmt.Println(s1)               //[1 2 3 4 5]
    //fmt.Println(s2)               //[3 4 5 100]
    //fmt.Println(cap(s1), cap(s2)) //5 6
    //fmt.Println(len(s1), len(s2)) //5 4

    //ex5
    //var arr = [...]int{1, 2, 3, 4, 5}
    //slice := arr[:2]                  //[1 2] len:2 cap:5
    //slice = append(slice, 6, 7)       //没扩容 切片是:[1 2 6 7] len:4 cap:5
    //slice[0] = 100                    //[100 2 6 7]
    //fmt.Println(arr)                  //[100 2 6 7 5]
    //fmt.Println(slice)                //[100 2 6 7]
    //fmt.Println(cap(arr), cap(slice)) //5 5
    //fmt.Println(len(arr), len(slice)) //5 4

    //ex6
    var arr = [...]int{1, 2, 3, 4, 5}
    slice := arr[:2] // [1 2] len:2 cap:5

    slice = append(slice, 6, 7, 8, 9, 10) //底层数组扩容了,切片是[1 2 6 7 8 9 10]
    slice[0] = 100                        //[100 2 6 7 8 9 10]

    fmt.Println(arr)                  //[1 2 3 4 5]
    fmt.Println(slice)                //[100 2 6 7 8 9 10]
    fmt.Println(cap(arr), cap(slice)) //5 10
    fmt.Println(len(arr), len(slice)) //5 7
}