6 切片

  • 数组长度是固定的,数组的长度也属于类型的一部分,有许多局限性

    • 例如,函数传参时只能支持一种数组类型
    • 例如,不能再继续向数组中添加元素
  • Slice 是一个拥有相同类型元素的可变长度序列

    • 是基于数组类型做的一层封装
    • 十分灵活,支持自动扩容
  • 切片是一个 引用类型,不支持直接比较,只能和nil比较

    • 内部结构包含 地址长度容量
  • 一般用于快速的操作一块数据集合

切片的定义

  • 基本语法:var name []T

    • name : 变量名
    • T : 切片中的元素类型
  • 示例:

    • ```
      var a []string
      var b = []int{}
      var c = []bool{false, true}
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34



      #### 切片的长度和容量

      * 切片拥有自己的长度和容量:
      * `len()` 求长度
      * `cap()` 求容量

      #### 切片的表达式

      * 切片表达式 从`字符串、数组、指向数组或切片的指针` 构造 子字符串或切片
      * 两种变体
      * 指定`low`和`high`两个索引界限值的简单形式
      * 除了`low`和`high`索引界限值外,还指定容量 的完整形式

      ##### 简单切片表达式

      * 切片的底层就是一个数组

      * 可以**基于 数组** 通过切片表达式得到切片

      * `[low , high)`: 左包含,右不包含

      * 得到的**切片长度** `high - low`

      * 得到的**切片容量是** 操作数数组下标从`low` 到最后一个元素所组成的数组的长度

      * 示例:

      * ```go
      a := [5]int{1,2,3,4,5}
      s := a[1:3]
      fmt.Pritf("s:%v len(s):%v cap(s): %v",s,len(s),cap(s))
  • 切片表达式中的任何索引可以省略:

    • low默认为 0
    • high默认为 切片操作数的长度
    • a[2:] : high = len(a)
    • a[:3]: low = 0
    • a[:] low = 0, high = len(a)
  • ==注意:==

    • 对于数组和字符串:若0<=low<=high<=len(a) 则索引合法,否则索引越界,运行时会panic
    • 对 切片 再执行切片表达式(切片在切片时),
      • high 的上限是 切片的容量cap(s),而非长度len(s)
    • 常量索引必须是非负,int类型
完整切片表达式
  • 数组,指向数组的指针,或切片 支持完整切片表达式
    • 注意,字符串不支持
  • 格式 a[low : high : max]
  • 构造与简单切片表达式a[low,high]
    • 相同类型、相同长度、相同元素
    • 将切片的容量设置为max-low
  • 在完整切片表达式中,只有第一个索引值low可以省略,默认为 0
    • 示例
1
2
3
a := [5]int{1,2,3,4,5}
t := a[1:3:5]
//t: [2 3] len(t):2 cap(t):4
  • 完整切片表达式 需满足
    • 0 <= low <= high <= max <= cap(a)

使用 make() 函数构造切片

  • 除了基于数组构造切片,还可以动态创建切片

  • make 格式:make ([]T, size, cap)

    • T : 切片内元素类型
    • size :切片中元素数量
    • cap :切片容量
  • 示例:

    • ```go
      a := make([]int, 2, 10)
      //a: [0 0] len(a):2 cap(a): 10

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      * a切片的内部存储空间分配了10,实际上只用了2个

      * 容量不会影响当前元素的个数



      #### 切片的本质

      * **本质:**对底层数组的封装,包含三个信息

      * 底层数组的指针
      * 切片的长度
      * 切片的容量

      * 例如

      ```go
      a := [8]int{0,1,2,3,4,5,6,7}
      s1 := a[:5]
    • slice_01

    • 切片s2 := a[3:6]

      • slice_02

判断切片是否为空

  • 最好使用切片的长度 len(s)==0 来判断

切片不能直接比较

  • 不能使用 == 操作符判断切片是否全部元素相等

  • 切片唯一合法的比较操作:和nil比较

  • 一个nil值的切片,没有底层数组

    • 切片长度和容量都是 0
  • 但是一个长度和容量都是0的切片不一定是nil

    • 示例

    • ```go
      var s1 []int //len(s1)=0;cap(s1)=0;s1==nil
      s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil
      s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19



      * 因此判断切片是否为空主要用`len(s)==0`



      ### 切片的赋值拷贝

      * 一个切片 **拷贝赋值** 另一个切片,两个切片会共享底层数组,因此对一个切片的修改会影响另一个切片

      * 示例

      * ```go
      s1 := make([]int, 3) //[0 0 0]
      s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
      s2[0] = 100
      fmt.Println(s1) //[100 0 0]
      fmt.Println(s2) //[100 0 0]

切片遍历

  • 支持下标索引遍历和for range键值遍历

append() 方法为切片添加元素

  • Go 中的内置函数append() 可以为切片动态添加元素

    • 添加一个:s = append(s,1)
    • 添加多个:s = append(s,1,2,3)
    • 添加另一个切片中的元素 :s = append(s, s2...)
  • ==注意:==通过var声明零值切片 可以在append() 直接使用,无需初始化

    • ```go
      var s []int //声明后直接使用
      s = append(s,1,2)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39

      * 每个切片指向一个**底层数组**,

      * 这个数组的**容量够用**就直接在该数组新增元素
      * 当**容量不够用**时,切片**按照一定的规则进行扩容**。此时该切片指向的**底层数组会更换**
      * **扩容操作**发生在`append`时,因此需要用原变量接收函数返回值

      * `append`:

      * 将元素追加到切片的最后并返回该切片
      * 切片扩容的规则都是扩容前的两倍



      ### 切片的扩容策略

      * 查看源码:

      * ```go
      newcap := old.cap
      doublecap := newcap + newcap
      if cap > doublecap {
      newcap = cap
      } else {
      if old.len < 1024 {
      newcap = doublecap
      } else {
      // Check 0 < newcap to detect overflow
      // and prevent an infinite loop.
      for 0 < newcap && newcap < cap {
      newcap += newcap / 4
      }
      // Set newcap to the requested cap when
      // the newcap calculation overflowed.
      if newcap <= 0 {
      newcap = cap
      }
      }
      }
      1. 首先判断,若新申请(cap)大于两倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量

      2. 否则判断,若旧切片的长度小于1024,最终容量就是旧容量的两倍,

      3. 否则判断,若旧切片的长度大于等于1024,最终容量从旧容量开始循增加原来的1/4,直到最终容量大于等于新申请的容量

      4. 如果最终容量计算值溢出,则最终容量就是新申请的容量

  • 注意:切片扩容会根据切片中元素类型的不同而作不同处理

使用 copy() 函数复制切片

  • 由于切片是引用类型,拷贝赋值时,两个切片都指向同一块内存地址

    • 示例

    • ```go

      a := []int{1, 2, 3, 4, 5}
      b := a
      fmt.Println(a) //[1 2 3 4 5]
      fmt.Println(b) //[1 2 3 4 5]
      b[0] = 1000
      fmt.Println(a) //[1000 2 3 4 5]
      fmt.Println(b) //[1000 2 3 4 5]
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21



      * 可以使用内置函数`copy()`迅速地将一个切片的数据复制到另外一个切片空间中,

      * `copy(destSlice, srcSlice []T)`

      * srcSlice:数据来源切片
      * dextSlice:目标切片

      * 示例:

      * ```go
      a := []int{1, 2, 3, 4, 5}
      c := make([]int, 5, 5)
      copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
      fmt.Println(a) //[1 2 3 4 5]
      fmt.Println(c) //[1 2 3 4 5]
      c[0] = 1000
      fmt.Println(a) //[1 2 3 4 5]
      fmt.Println(c) //[1000 2 3 4 5]

从切片中删除元素

  • Go 中无删除切片元素的专用方法,需要使用切片本身特性:

    • 若要删除下表为index: s = append(s[:index],s[index+1:]...)

    • ```go
      func main() {

      // 从切片中删除元素
      a := []int{30, 31, 32, 33, 34, 35, 36, 37}
      // 要删除索引为2的元素
      a = append(a[:2], a[3:]...)
      fmt.Println(a) //[30 31 33 34 35 36 37]
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17



      ### 练习题

      #### 练习一

      请写出下面代码的输出结果。

      ```go
      func main() {
      var a = make([]string, 5, 10)//a : [0 0 0 0 0 0 0 0 0 0 ],len 5, cap 10
      for i := 0; i < 10; i++ {
      a = append(a, fmt.Sprintf("%v", i)) //[0 0 0 0 0 "0" "1" ..."9"]
      }
      fmt.Println(a)
      }
  • 直接分析:[0 0 0 0 0 “0” “1” …”9”]

  • run :[ 0 1 2 3 4 5 6 7 8 9]

    • 前面五个空字符串

练习二

请使用内置的sort包对数组var a = [...]int{3, 7, 8, 9, 1}进行排序(附加题,自行查资料解答)。

  • sort 包主要针对[]int., []float64,[]string 以及其他自定义切片的排序

    • 内部实现了:插入排序、归并排序、堆排序、快速排序

    • sort 会依据实际数据自动选择最优的排序方法

    • 我们只需要考虑实现 sort.Interface类型

      • ```go
        type Interface interface {
        Len() int           // Len方法返回集合中的元素个数
        Less(i, j int) bool // i>j,该方法返回索引i的元素是否比索引j的元素小、
        Swap(i, j int)      // 交换i, j的值
        
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28

        * 例如:

        * ```go
        package main

        import (
        "fmt"
        "sort"
        )

        type IntSlice []int

        func (s IntSlice) Len() int {
        return len(s)
        }
        func (s IntSlice) Swap(i, j int){
        s[i], s[j] = s[j], s[i]
        }
        func (s IntSlice) Less(i, j int) bool {
        return s[i] < s[j]
        }

        func main() {
        a := []int{4,3,2,1,5,9,8,7,6}
        sort.Sort(IntSlice(a))
        fmt.Println("After sorted: ", a)
        }