__都非拉得的博客

Posted on 08 May 2018

切片是一种数据结构, 这种数据结构便于使用和管理数据集合.

切片是围绕动态数组的概念构建的, 可以按需自动增长和缩小.

切片的动态增长是通过内置函数append来实现的. 这个函数可以快速且高效的增长切片, 还可以通过对切片再次切片来缩小一个切片的大小.

切片有3个字段:

  1. 指向底层数组的指针
  2. 切片访问的元素的个数(即长度)
  3. 切片允许增长到的元素的个数(即容量)

创建和初始化

  1. make

     // 创建一个字符串切片, 长度容量都是5个元素
     slice := make([]string, 5)
    
     // 创建一个整型切片, 长度为3个元素, 容量为5个元素
     slice := make([]int, 3, 5)
    

    要点:

    • 使用make时, 需要传入一个参数, 指定切片的长度
    • 不允许创建容量小于长度的切片.
  2. 切片字面量

     // 创建字符串切片, 长度容量都是5
     slice := []string {"Red", "Blue", "Green", "Yellow", "Pink"}
    
     // 创建整型切片, 长度容量都是3
     slice := []int {10, 20, 30}
    
    • 使用切片字面量创建切片时, 可以设置初始长度和容量. 要做的就是在初始化时, 给出所需的长度和容量作为索引.
     // 创建字符串切片, 使用空字符串初始化第100个元素
     slice := []string {99:""}
    

使用切片

  1. 赋值和切片

     slice := []int {10, 20, 30, 40, 50}
    
     // 改变索引为1的元素的值
     slice[1] = 25
    
     ------------------------------
     // 使用切片创建切片
     slice := []int {10, 20, 30, 40, 50}
     // 创建一个新切片, 长度为2, 容量为4
     newSlice := slice[1:3] // 20, 30
    
  2. 切片增长

     slice := []int {10, 20, 30, 40, 50}
    
     newSlice := slice[1:3]
    
     newSlice = append(newSlice, 60)
    
  3. 创建切片时的3个索引

  4. 迭代切片

     slice := []int{10, 20, 30, 40}
    
     // for ... range
     for i, v := range slice {
         fmt.Printf("i, v \n= %d, %d", i, v)
     }
    
  5. 删除切片元素

     // 1. 删除slice中间的某个元素并保存原有的元素顺序
     func remove(slice []int, i int) []int {
         copy(slice[i:], slice[i+1:])
         return slice[:len(slice)-1]
     }
    
     func main() {
         s := []int{5, 6, 7, 8, 9}
         fmt.Println(remove(s, 2)) // "[5 6 8 9]"
     }
    
     // 2. 如果删除元素后不用保持原来顺序的话,我们可以简单的用最后一个元素覆盖被删除的元素:
     func remove(slice []int, i int) []int {
         slice[i] = slice[len(slice)-1]
         return slice[:len(slice)-1]
     }
    
     func main() {
         s := []int{5, 6, 7, 8, 9}
         fmt.Println(remove(s, 2)) // "[5 6 9 8]
     }
    
    • range 创建了每个元素的副本, 而不是直接返回对该元素的引用.

    • 有两个特殊的内置函数len和cap, 可以用于处理数组,切片和通道. 对于切片, len返回长度, cap返回容量

在函数间传递切片

在函数间传递切片就是要在函数间以值的方式传递切片.

slice := make([]int, le6)

slice = foo(slice)

func foo(slice []int) []int {
    ...
    return slice
}

在64位架构的机器上, 一个切片需要24字节的内存: 指针字段需要8个字节, 长度和容量分别需要8字段.

  1. slice之间不能比较, 因此我们不能使用==操作符来判断两个slice是否含有全部相等元素.不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较. slice唯一合法的比较操作是和nil比较.
     slice不直接支持比较运算符: 
     第一个原因,一个slice的元素是间接引用的,一个slice甚至可以包含自身。
     第二个原因,因为slice的元素是间接引用的,一个固定的slice在不同的时刻可能包含不同的元素,因为底层数组的元素可能会被修改。
    
  2. 如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了slice,因为新slice的长度会变大

  3. append函数工作原理

    • 每次调用append函数,必须先检测slice底层数组是否有足够的容量来保存新添加的元素。如果有足够空间的话,直接扩展slice(依然在原有的底层数组之上),将新添加的元素复制到新扩展的空间,并返回slice。如果没有足够的增长空间的话,append函数则会先分配一个足够大的slice用于保存新的结果,先将输入复制到新的空间,然后添加元素。

    • 函数append会智能地处理底层数组的容量增长. 在切片的容量小于1000个元素时, 总是会成倍的增加容量. 一旦元素个数超过1000, 容量的增长因子会设为1.25, 也就是说每次增加25%的容量.