zoukankan      html  css  js  c++  java
  • Go语言中的切片(十)

    go中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。于是切片就来了,切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

    定义切片

    切片的定义跟数组很类似,区别就是不需要指定长度,如下:

    var a []int
    
    var 切片名 []切片值的类型

    举例:

    func main() {
        var a []int // 定义一个整型切片
        var b = []int{} // 定义一个整型切片并初始化
        var c = []bool{true,false} // 定义一个布尔切片并初始化
        fmt.Println(a) // []
        fmt.Println(b) // []
        fmt.Println(c) // [true false]
    }

    需要注意的是,定义的切片如果没有初始化,是不可以直接操作的。如:

    func main() {
        var a []int  // 定义了切片,但没初始化
        a[0] = 100 // 直接赋值
        fmt.Println(a) // 报错
    }

    基于数组定义切片

    切片的底层就是一个数组,所以可以基于数组定义切片:

    func main() {
        var a = [5]int{1,2,3,4,5}
        var b = a[1:3]
        fmt.Println(b)  // [2,3]
        fmt.Printf("b的类型:%T",b) //b的类型:[]int
    }

    代码中 a[1:3] 表示获取从数组索引 1 到 3 的值,包含开始索引的值,不包含结束索引的值,这样就能获取到一个切片。还支持以下操作:

    var a = [5]int{1,2,3,4,5}
    var b = a[1:] // [2 3 4 5]
    var c = a[:3] // [1 2 3]
    var d = a[:]  // [1 2 3 4 5]
            

    切片的三要素

    所谓的三要素是我自己觉得的,分别是 大小,容量和地址。

     切片大小

    切片的大小指的就是当前切片的长度,可以使用 len() 方法来获取:

    func main() {
        var a = []int{1,2,3,4,5}
        fmt.Printf("a 的大小是:%v",len(a))  // a 的大小是:5
    }

    切片的容量

    切片的容量可以理解为底层数组最大能存放的元素的个数,使用 cap() 方法来获取,例子:

    func main() {
        var a = [...]string{"北京","上海","广州","深圳","杭州","成都","重庆"} // 定义字符串数组
        var b = a[1:5] // 基于数组定义切片
        fmt.Printf("切片b的容量:%v",cap(b))  // 切片b的容量:6
    }

    切片b是从数组a的索引 1 开始的,所以切片的容量是 6。容量是可变的,即切片支持扩容,可以是使用 append() 方法向切片添加项,例子:

    func main() {
        var a = [...]string{"北京","上海","广州","深圳","杭州","成都","重庆"} // 定义字符串数组
        var b = a[1:5] // 基于数组定义切片
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b)) 
        b = append(b,"合肥")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
        b = append(b,"长沙")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
        b = append(b,"苏州")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
    }
    
    
    // 切片b的长度:4,切片b的容量:6 
    // 切片b的长度:5,切片b的容量:6 
    // 切片b的长度:6,切片b的容量:6 
    // 切片b的长度:7,切片b的容量:12

    从结果分析,每一次append后,切片的长度加 1,而容量如果大于长度时则不变,如果长度超过容量时,容量则会扩容,且每一次扩容都是2倍增长(一个一个的append时是2倍,别的情况不一定)。

    直接定义的切片是一样满足的以上条件的:

    func main() {
        var b = []string{"北京","上海","广州"} //直接定义切片
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
        b = append(b,"合肥")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
        b = append(b,"长沙")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
        b = append(b,"苏州")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v 
    ",len(b),cap(b))
    }
    
    // 切片b的长度:3,切片b的容量:3 
    // 切片b的长度:4,切片b的容量:6 
    // 切片b的长度:5,切片b的容量:6 
    // 切片b的长度:6,切片b的容量:6 

    切片的地址

    切片地址是指切片中第一个元素指向的内存空间,本质为底层数组第一个元素的地址,可以使用占位符 %p 来打印,例子:

    func main() {
        var b = []string{"北京","上海","广州"} //直接定义切片
        fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p 
    ",len(b),cap(b),b)
        b = append(b,"合肥")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p 
    ",len(b),cap(b),b)
        b = append(b,"长沙")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p 
    ",len(b),cap(b),b)
        b = append(b,"苏州")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p 
    ",len(b),cap(b),b)
        b = append(b,"南京")
        fmt.Printf("切片b的长度:%v,切片b的容量:%v,切片b的地址:%p 
    ",len(b),cap(b),b)
    }
    
    
    // 切片b的长度:3,切片b的容量:3,切片b的地址:0xc000072180 
    // 切片b的长度:4,切片b的容量:6,切片b的地址:0xc00005a0c0 
    // 切片b的长度:5,切片b的容量:6,切片b的地址:0xc00005a0c0 
    // 切片b的长度:6,切片b的容量:6,切片b的地址:0xc00005a0c0 
    // 切片b的长度:7,切片b的容量:12,切片b的地址:0xc00009c000

    从结果可以看到,当切片容量改变时,切片地址也会改变。这是因为当切片要扩容时,底层数组容量也就不够了,要将原数组进行扩容,扩容后由于要求数组的地址空间连续,因此原地址不满足条件,会在开辟一片新的空内存区域存放扩容后的数组,因此地址也就变了。

    切片是引用类型

    数组是值类型,但基于数组封装的切片却是引用类型,例子:

    func main() {
        var a = []int{1,2,3,4,5}
        var b = a
        fmt.Printf("b:%v
    ",b)
        b[0] = 100
        fmt.Printf("修改后的b: %v,此时a是:%v
    z",a,b)
    }
    
    // b:[1 2 3 4 5]
    // 修改后的b: [100 2 3 4 5],此时a是:[100 2 3 4 5]

    引用类型保存的是内存地址,所以当b赋值为a时,引用的是同一个内存地址,当b修改时,a也会改变。

    使用copy()函数复制切片

    上面说到切片是引用类型,所以不能直接把一个切片赋值给另一个切片而到达复制切片的目的。好在go语言内置了 copy() 函数,可以迅速地将一个切片的数据复制到另外一个切片空间中。其使用方式如下:

    copy(目标切片,数据源切片)

    举个例子:

    func main() {
        a:=[]int{1,3,5,7}
        b := make([]int,4,4)
        copy(b,a)
        fmt.Println(a) // [1 3 5 7]
        fmt.Println(b) // [1 3 5 7]
        a[0] = 100
        fmt.Println(a) // [100 3 5 7]
        fmt.Println(b) // [1 3 5 7]
    }

    使用make()函数构造切片

    切片可以是自定义,可以基于数组定义,也可以基于切片再切片。现在还可以使用make()函数来动态创建切片。格式如下:

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

    举个例子:

    func main() {
        a:=make([]int,3,5)
        fmt.Println(a)
        fmt.Printf("a 的长度:%v
    ",len(a))
        fmt.Printf("a 的容量:%v",cap(a))
    }
    
    // [0 0 0]
    // a 的长度:3
    // a 的容量:5

    遍历切片

    切片的遍历跟数组是一致的,支持索引遍历和 for range遍历:

    func main() {
        a:=[]int{1,3,5,7}
    
        for i:=0;i<len(a);i++{
            fmt.Println(i)
        }
    
        for idx,val := range a{
            println(idx,val)
        }
    }

    删除切片中的元素

    Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。

    func main() {
        a:=[]int{1,3,5,7,9,11,13,15,17}
        // 删除索引为2的值
        b:= append(a[:2],a[3:]...)
        fmt.Println(b) // [1 3 7 9 11 13 15 17]
    }

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

    最后

    切片的相关内容到此为止,可能说的不完整,后面学到再补充。

  • 相关阅读:
    junit所需要的jar包
    【SSH学习笔记】用Struts2实现简单的用户登录
    HIbernate 一对多 多对多
    Hibernate 干货2
    Hibernate 干货
    hibernate学习笔记
    Hibernate 框架学习
    Class的isAssignableFrom方法
    dubbo源码分析1——SPI机制的概要介绍
    Dubbo简介
  • 原文地址:https://www.cnblogs.com/wjaaron/p/11724549.html
Copyright © 2011-2022 走看看