Slice

@爱耍流氓的唐僧  January 12, 2021

切片的定义:
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。
切片的语法:

var name []T
name:表示变量名
T:表示切片中的元素类型

样例:

package main
func main() {
    // 声明切片类型
    var a []string              //声明一个字符串切片,没有分配内存,==nil
    var b = []int{}             //声明一个整型切片并初始化
    var c = []bool{false, true} //声明一个布尔切片并初始化
    var d = []bool{false, true} //声明一个布尔切片并初始化
    fmt.Println(a)              //[]
    fmt.Println(b)              //[]
    fmt.Println(c)              //[false true]
    fmt.Println(a == nil)       //true
    fmt.Println(b == nil)       //false
    fmt.Println(c == nil)       //false
    // fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
}

切片的长度和容量:
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的low和high表示一个索引范围(左包含,右不包含),也就是下面代码中从数组a中选出1<=索引值<4的元素组成切片s,得到的切片长度=high-low,容量等于得到的切片的底层数组的容量。
例子:

func main() {
    a := [5]int{1, 2, 3, 4, 5}
    s := a[1:3]  // s := a[low:high]
    fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
}

输出:

s:[2 3] len(s):2 cap(s):4

切片在切片

slice[i:j:k]
其中 i 表示从 slice 的第几个元素开始切,j 控制切片的长度(j-i),k 控制切片的容量(k-i),如果没有给定 k,则表示切到底层数组的最尾部。下面是几种常见的简写形式:
slice[i:]  // 从 i 切到最尾部
slice[:j]  // 从最开头切到 j(不包含 j)
slice[:]   // 从头切到尾,等价于复制整个 slice

切片的本质:
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。通过一个框来切出自己需要部分,举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下:
slice_01.png
切片s2 := a[3:6],相应示意图如下:
slice_02.png
判断切片是否为空
要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断。
使用make()函数构造切片
我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:

make([]T, size, cap)
其中:
T:切片的元素类型
size:切片中元素的数量
cap:切片的容量

切片在没有使用make或者初始化时,只是做了定义并没有分配相应的空间所以就是'零值-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

切片的赋值拷贝
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

func main() {
    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]
}

Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

func main(){
    var s []int
    s = append(s, 1)        // [1]
    s = append(s, 2, 3, 4)  // [1 2 3 4]
    s2 := []int{5, 6, 7}  
    s = append(s, s2...)    // [1 2 3 4 5 6 7]
}

通过var声明的零值切片可以在append()函数直接使用,无需初始化。

var s []int
s = append(s, 1, 2, 3)

每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。

func main() {
    //append()添加元素和切片扩容
    var numSlice []int
    for i := 0; i < 10; i++ {
        numSlice = append(numSlice, i)
        fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
    }
}

结果:

[0]  len:1  cap:1  ptr:0xc0000a8000
[0 1]  len:2  cap:2  ptr:0xc0000a8040
[0 1 2]  len:3  cap:4  ptr:0xc0000b2020
[0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

切片是引用类型,Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中(传值类型),copy()函数的使用格式如下:

copy(destSlice, srcSlice []T)
其中:
srcSlice: 数据来源切片
destSlice: 目标切片

从切片中删除元素

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

总结一下就是:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)
微信图片_20210112111555.png

切片其实就是一个框,对底层的数组进行相应的框选操作。

当对切片进行 append 操作,导致长度超出容量时,就会创建新的数组,这会导致和原有切片的分离。在下例中

package main

import "fmt"

var hello string
func main(){
    mm := [5]int{1,2,4,5,6}
    nn := mm[:3]
    fmt.Println(nn)
    nn = append(nn,99,100,8888,222)
    fmt.Println(mm)
    fmt.Println(nn)
    nn[0] = 1111
    fmt.Println(mm)
    fmt.Println(nn)
}

输出

[1 2 4]
[1 2 4 5 6]
[1 2 4 99 100 8888 222]//这时append已经超出默认的容量,nn则只想一个新的切片,不在指向底层的mm,下面再次打印的时候可以看出来
[1 2 4 5 6]
[1111 2 4 99 100 8888 222]

添加新评论