golang-切片

引子

因为数组的长度是固定的并且数组的长度属于类型的的一部分,所以数组有很多的局限性,例如:

func arraySum(x [3]int) int{
    sum := 0
    for _, v := range x{
        sum = sum + v
    }
    return sum
}

这个求和函数稚嫩接收长度为[3]int 的数组元素,其他的都不支持

如:a := [3]int{1, 2, 3}

数组a中已经有3个元素了,不能继续在往数组里面继续添加新元素

切片

切片(slice)是一个拥有相同类型元素可变长度的序列。它是基于数组类型做的一层封装。它非常的灵活,支持自动扩容。

切片是一个引用类型,它的内部结构包含 地址长度容量。切片一般用于快速地操作一块数据集合。

 

切片的定义

声明切片的类型的基本语法如下

1 var name []T

其中:

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

举个例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     // 声明切片的类型
 7     var a []string              //声明一个字符串切片
 8     var b = []int{}             // 声明一个int类型slice并进行初始化
 9     var c = []bool{true, false} // 声明一个bool类型slice并进行初始化
10     var d = []bool{true, false} // 声明一个bool类型slice并进行初始化
11     fmt.Println(a)              //[]
12     fmt.Println(b)              //[]
13     fmt.Println(c)              //[true false]
14     fmt.Println(a == nil)       //true
15     fmt.Println(b == nil)       //false
16     fmt.Println(c == nil)       //false
17     // fmt.Println(c == d)   //切片是引用类型,不支持直接比较,只能和nil比较
18 
19 }

切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量

切片表达式

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

简单切片表达式

切片的底层就是一个数组,所以我们可以基于数组通过切片的表达式得到切片。切片表达始终的low合high表示一个索引范围(左包含右不包含),也就是下面代码中从数组中选出1<=索引值<4的元素组成切片s,得到切片长度=high-low,容量等于得到的切片底层数组的容量。

package main

import "fmt"

func slice1() {
    // 切片的长度和容量
    a := [5]int{1, 2, 3, 4, 5}
    s := a[1:3]
    fmt.Println(s, len(s), cap(s))  // [2 3] 2 4
}

输出:

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

为了方便起见,可以省略切片表达式中的任何索引。省略了low则默认为0;省略了high则默认为切片操作数的长度

a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]

注意:

对于数组或字符串,如果0 <= low <= high <= len(a),则索引合法,否则就会索引越界(out of range)。

对切片再执行切片表达式时(切片再切片),high的上限边界是切片的容量cap(a),而不是长度。常量索引必须是非负的,并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。如果lowhigh两个指标都是常数,它们必须满足low <= high。如果索引在运行时超出范围,就会发生运行时panic

 

切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:地城数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组 a:= [8]int{0,1,2,3,4,5,6,7},切片 s1 := a[:5],相应示例图如下。

 切片s2 := a[3:6] 相应

 

判断切片是否为空

要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil 来判断

切片不能直接比较

切片与切片之间不能比较的,我们不能使用 == 操作符来判断两个切片是否含有全部相等元素,切片唯一合法的比较操作是和nil比较一个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!=nils

所以要判断一个切片是否是空的,要用len(s) == 0 来判断,不应该使用s==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]
}

切片遍历

 

切片的遍历方式和数组是一致的,支持索引遍历和for range遍历。

 1 func main() {
 2     s := []int{1, 3, 5}
 3 
 4     for i := 0; i < len(s); i++ {
 5         fmt.Println(i, s[i])
 6     }
 7 
 8     for index, value := range s {
 9         fmt.Println(index, value)
10     }
11 }

 

切片共享底层数据

切片共享底层数据的前提是切片不能进行扩容,共享底层数据的原理本质上就是共用一个切片的内存地址,如果其中某个切片的引用出现了扩容这个时候该切片的内存地址也会发生改变相当于copy了一份切片出来进行修改

func slice5() {
    // 切片共享底层数据
    a := []int{1, 2, 3}
    b := a
    fmt.Println(b) //[1 2 3]
    //b = append(b, 2, 3, 4)
    b[1] = 200
    fmt.Println(a) //[1 200 3]
    fmt.Println(b) //[1 200 3]

}


func slice6() {
    // 切片共享底层数据
    a := []int{1, 2, 3}
    b := a
    fmt.Println(b) //[1 2 3]
    b = append(b, 2, 3, 4)
    b[1] = 200
    a[0] = 100
    fmt.Println(a) //[100 2 3]
    fmt.Println(b) //[1 200 3 2 3 4]

}

 

posted @ 2023-11-29 15:01  zyling_me  阅读(1)  评论(0编辑  收藏  举报