Go 切片

6 切片

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

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

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

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

切片的定义

  • 基本语法:var name []T

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

    • var a []string
      var b = []int{}
      var c = []bool{false, true}
      
切片的长度和容量
  • 切片拥有自己的长度和容量:
    • len() 求长度
    • cap() 求容量
切片的表达式
  • 切片表达式 从字符串、数组、指向数组或切片的指针 构造 子字符串或切片
  • 两种变体
    • 指定lowhigh两个索引界限值的简单形式
    • 除了lowhigh索引界限值外,还指定容量 的完整形式
简单切片表达式
  • 切片的底层就是一个数组

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

  • [low , high): 左包含,右不包含

    • 得到的切片长度 high - low

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

    • 示例:

      • 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
    • 示例
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 :切片容量
  • 示例:

    • a := make([]int, 2, 10)
      //a: [0 0] len(a):2 cap(a): 10
      
    • a切片的内部存储空间分配了10,实际上只用了2个

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

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

    • 底层数组的指针
    • 切片的长度
    • 切片的容量
  • 例如

    a := [8]int{0,1,2,3,4,5,6,7}
    s1 := a[:5]
    
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBXK9Zgp-1618978669363)(E:\LearningNotes\Go\GoLearning.assets\slice_01.png)]
    • 切片s2 := a[3:6]
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbwgJpKk-1618978669365)(E:\LearningNotes\Go\GoLearning.assets\slice_02.png)]
判断切片是否为空
  • 最好使用切片的长度 len(s)==0 来判断

切片不能直接比较

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

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

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

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

    • 示例

    • 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
      
  • 因此判断切片是否为空主要用len(s)==0

切片的赋值拷贝

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

    • 示例

    • 	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() 直接使用,无需初始化

    • var s []int   //声明后直接使用
      s = append(s,1,2)
      
  • 每个切片指向一个底层数组

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

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

切片的扩容策略

  • 查看源码:

    • 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() 函数复制切片

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

    • 示例

    • 	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]
      
  • 可以使用内置函数copy()迅速地将一个切片的数据复制到另外一个切片空间中,

    • copy(destSlice, srcSlice []T)

      • srcSlice:数据来源切片
      • dextSlice:目标切片
    • 示例:

    • 	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:]...)

    • 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]
      }
      

练习题

练习一

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

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类型

      • type Interface interface {
            Len() int           // Len方法返回集合中的元素个数
            Less(i, j int) bool // i>j,该方法返回索引i的元素是否比索引j的元素小、
            Swap(i, j int)      // 交换i, j的值
        }
        
        
      • 例如:

        • 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)
          }
          
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值