• 爱上开源之golang入门至实战第四章-切片(Slice)


    前言

    Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

    在go语言里,切边是一个应用极为广泛的数据对象;所以在学习golang的过程中,一定要好好的掌握切片的相关知识。

    4.2.2 切片

    Go 语言切片是对数组的抽象。一个slice类型一般写作[]T,其中T代表slice中元素的类型。

    slice的语法和数组很像,只是没有固定长度而已。数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列 (或者全部)元素的功能,slice的**底层引用一个数组对象**。

    `slice`(切片)代表变长的序列,序列中每个元素都有相同的类型。

    Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

    4.2.2.1 声明切片

    你可以声明一个未指定大小的数组来定义切片:

    var slice_01 []type

    在语法上和上面的数组比较的相似;差别就在于切片不需要说明长度。

    也可以使用 make() 函数来创建切片:

    var slice_01 []type = make([]type, len)
    ​
    或为
    ​
    slice_01 := make([]type, len)

    在切片的make声明和初始话的方式中;也可以指定容量,

    比如

    make([]T, length, capacity)

    其中 capacity 为可选参数。通过上面的方式就可以声明和初始话一个容量大小为capacity,当前长度为length的切片,切片的用法如何使用过python语言的话,这个切片的数据类型可以实现的功能基本上和python语言里slice函数的非常的相似

    需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引⽤数组⽚段,以实现变⻓⽅案。

    runtime.h

    #runtime.h
    ​
    struct Slice
    ​
    { // must not move anything
    ​
     byte* array; // actual data
    ​
     uintgo len; // number of elements
    ​
     uintgo cap; // allocated number of elements
    ​
    };
    ​
    ​

    以上是golang里slice的源代码片段;可以通过这个slice的结构体源代码的定义,发现在golang里,slice是通过内部属性和指针的方式,来实现了变长的方案。

    • 引⽤类型。但⾃⾝是结构体,值拷⻉传递。

    • 属性 len 表⽰可⽤元素数量,读写操作不能超过该限制。

    • 属性 cap 表⽰最⼤扩张容量,不能超出数组限制。

    • 如果 slice == nil,那么 len、cap 结果都等于 0。

    4.2.2.2 切片初始化

    slice_01 :=[] int {1,2,3 }

    通过初始化语句:=直接进行初始化, 其中[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。通过以上语句,实现了初始化slice_01对象为切片对象,并且长度和容量都为3;其中元素依次为1,2,3的int切片。

    4.2.2.3 len和cap

    切片是可索引的,并且可以由 len() 方法获取长度。切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。

    x := make([]int,3,5)
    fmt.Printf("len=%d cap=%d slice=%v**\n**",len(x),cap(x),x)
    ​
    ​
    ====OUTPUT====
    len=3 cap=5 slice=[0 0 0]

    一个切片在未初始化之前默认为 nil,长度为 0,如下:

    var x []int
    fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
    ​
    ​
    ====OUTPUT====
    len=0 cap=0 slice=[]

    4.2.2.4 slice

    s := arr[startIndex:endIndex] 

    将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。

    s := arr[startIndex:] 

    默认 endIndex 时将表示一直到arr的最后一个元素。

    s := arr[:endIndex] 

    默认 startIndex 时将表示从 arr 的第一个元素开始。

    样例

    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    ​
    expression  slice                   len     cap     
    ------------+----------------------+------+-------
    data[:6:8]  [0 1 2 3 4 5]           6       8       
    data[5:]    [5 6 7 8 9]             5       5 
    data[:3]    [0 1 2]                 3       10 
    data[:]     [0 1 2 3 4 5 6 7 8 9]   10      10
    data[:11:12] Error  

    读写操作实际⺫标是底层数组,只需注意索引号的差别。

    data := [...]int{0, 1, 2, 3, 4, 5}
    s := data[2:4]
    s[0] += 1020
    s[1] += 2020
    fmt.Println(s)
    fmt.Println(data)

    输出:

    [1022 2023]
    [0 1 1022 2023 3 4 5]

    s1 := s[startIndex:endIndex] 

    通过切片 s 初始化切片 s1。

    对于是基于已有 slice 创建新 slice 对象,特别注意要保证切片的 cap 允许范围内调整属性。

    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    s1 := s[2:5] // [2 3 4]         len=3  cap=8
    s2 := s1[2:6:7] // [4 5 6 7]    len=3  cap=5
    s3 := s2[3:6] // Error

    切片操作后新对象依旧指向原底层数组。

        s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
        s1 := s[2:5] // [2 3 4]
        s1[2] = 1020
        fmt.Println(s1)
        s2 := s1[2:6] // [1020 5 6 7]
        s2[0] = 2010
        s2[3] = 2020
        fmt.Println(s2)
        fmt.Println(s)
    ​
    ====OUTPUT====
    ​
    [2 3 1020]
    [2010 5 6 2020]
    [2 3 2010]
    [0 1 2 3 2010 5 6 2020 8 9]

    4.2.2.5 append

    向 slice 尾部添加数据,返回新的 slice 对象。

    s := make([]int, 0, 5)
    fmt.Printf("%p\n", &s)
    s2 := append(s, 1)
    fmt.Printf("%p\n", &s2)
    fmt.Println(s, s2)
    ​
    ====OUTPUT=====
    0xc000004498
    0xc0000044c8
    [] [1]
    ​

    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    s := data[:3]
    s2 := append(s, 100, 200) // 添加多个值。
    fmt.Println(data)
    fmt.Println(s)
    fmt.Println(s2)
    ​
    ====OUTPUT=====
    [0 1 2 100 200 5 6 7 8 9]
    [0 1 2]
    [0 1 2 100 200]

    ⼀旦超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。

    data := [...]int{0, 1, 2, 3, 4, 10: 0}
    s := data[:2:3]
    s = append(s, 100, 200) // ⼀次 append 两个值,超出 s.cap 限制。
    fmt.Println(s, data) // 重新分配底层数组,与原数组⽆关。
    fmt.Println(&s[0], &data[0]) // ⽐对底层数组起始指针。
    ​
    ====OUTPUT=====
    [0 1 100 200] [0 1 2 3 4 5 6 7 8 9]
    0xc0001e21e0 0xc000022af0

    从输出结果可以看出,append 后的 s 重新分配了底层数组,并复制数据。如果只追加⼀个值,则不会超过 s.cap 限制,也就不会重新分配

    将上面程序改成下面的

    data := [...]int{0, 1, 2, 3, 4, 10: 0}
    s := data[:2:3]
    s = append(s, 100)
    fmt.Println(s, data)
    fmt.Println(&s[0], &data[0])
    s = append(s, 200)  
    fmt.Println(s, data) // 重新分配底层数组,与原数组⽆关。
    fmt.Println(&s[0], &data[0]) // ⽐对底层数组起始指针。
    ​
    ====OUTPUT=====
    [0 1 100] [0 1 100 3 4 5 6 7 8 9]
    0xc00011e9b0 0xc00011e9b0
    [0 1 100 200] [0 1 100 3 4 5 6 7 8 9]
    0xc00013dd70 0xc00011e9b0

    4.2.2.6 copy

    copy 函数可以实现在两个 slice 间复制数据,复制⻓度以 len⼩的为准。两个 slice 可指向同⼀底层数组,允许元素区间重叠。

    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    s := data[8:]
    s2 := data[:5]
    copy(s2, s) 
    fmt.Println(s2)
    fmt.Println(data)
    ​
    ====OUTPUT=====
    [8 9 2 3 4]
    [8 9 2 3 4 5 6 7 8 9]

    4.2.2.7 扩容

    当切片的长度超过容量的情况下,切片会进行扩容操作;通常以 2 倍容量重新分配底层数组。在⼤批量添加数据时,建议⼀次性分配⾜够⼤的空间,以减少内存分配和数据复制开销。或初始化⾜够⻓的 len 属性,改⽤索引号进⾏操作。及时释放不再使⽤的 slice 对象,避免持有过期数组,造成 GC ⽆法回收

    结束语

    在go语言里,切边是一个应用极为广泛的数据对象;所以在学习golang的过程中,一定要好好的掌握切片的相关知识。

  • 相关阅读:
    conda创建pytorch环境报错
    QML8、布局元素
    RocketMQ5.0源码分析-高可用组件AutoSwitchHAClient图文详解
    解决uniapp修改内置组件样式,在微信中不生效问题
    【王道】计算机网络传输层(四)
    文心一言 VS 讯飞星火 VS chatgpt (285)-- 算法导论21.2 4题
    quarkus实战之七:使用配置
    DFT specification file & string
    dm关键字提示报错
    HTML入门教程23:HTML脚本
  • 原文地址:https://blog.csdn.net/inthirties/article/details/126260015