zoukankan      html  css  js  c++  java
  • Go-slice

    slice (切片)

    Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型。它是基于数组类型做的一层封装。

    一个slice由三个部分构成:指针、长度和容量。

    • 指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。
    • 长度对应slice中元素的数目;长度不能超过容量
    • 容量一般是从slice的开始位置到底层数据的结尾位置。
    • 内置的len和cap函数分别返回slice的长度和容量。

    结构

    // %GOROOT%src
    untimesilce.go
    
    type slice struct {
    	array unsafe.Pointer
    	len   int
    	cap   int
    }
    

    指向底层数组的指针、切片的元素数量和底层数组的容量

    切片声明

    // 切片声明
    var varName []T
    
    // varName:表示变量名
    // T:表示切片中的元素类型
    
    // 切片与数组声明对比,数组指定元素数量
    // 数组声明语法
    var 数组变量名 [元素数量]Type
    

    切片初始化

    slice的字面值也可以按顺序指定初始化值序列,或者是通过索引和元素值指定,或者的两种风格的混合语法初始化。

    //声明一个整型切片并初始化
    var a = []int{}
    
    s1 := []int{0, 1, 2, 3, 4, 5}
    s2 := []int{0:1, 1:2, 2:4}
    s3 := []int{0:1, 1:2, 3}
    
    package main
    
    import "fmt"
    
    // 反转
    func reverse(s []int) {
    	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
    		s[i], s[j] = s[j], s[i]
    	}
    }
    
    func main() {
    	// 初始化数组a
        a := [...]int{0, 1, 2, 3, 4, 5}
        s1 := a[:]
    	reverse(a[:])
        fmt.Println(a) // 输出结果 [5 4 3 2 1 0]
        fmt.Println(s1) // [5 4 3 2 1 0]
        reverse(s1[:2])
        fmt.Println(s1) // [4 5 3 2 1 0]
        fmt.Println(a)  // [4 5 3 2 1 0] 底层数组元素也改变
        
    	// 初始化切片s
    	s := []int{0, 1, 2, 3, 4, 5}
    	reverse(s[:2]) // [1 0 2 3 4 5]
    	reverse(s[2:]) // [1 0 5 4 3 2]
    	reverse(s)
    	fmt.Println(s) // [2 3 4 5 0 1]
    }
    

    注意:slice类型的变量s和数组类型的变量a的初始化语法的差异。slice和数组的字面值语法很类似,它们都是用花括弧包含一系列的初始化元素,但是对于slice并没有指明序列的长度

    切片的创建

    由数组创建

    array[b:e],其中,array表示数组名;b表示开始索引,可以不指定,默认是0;e表示结束索引,可以不指定,默认是len(array)。array[b:e]表示创建一个包含e-b个元素的切片,第一个元素是array[b],最后一个元素是array[e-1]

    package main
    import "fmt"
    func main() {
        var array = [...]int{0,1,2,3,4,5,6} // 创建有7个 int 型元素的数组
        s1 := array[0:4]
        fmt.Printf("%v
    ", s1) // [0 1 2 3]
    }
    
    内置函数make创建

    内置的make函数动态创建一个指定元素类型、长度和容量的slice。切片各元素被默认初始化为切片元素类型的零值。

    格式
    make([]T, len)
    make([]T, len, cap) 
    
    例子
    package main
    
    import "fmt"
    
    func main() {
        a := make([]int, 3)
        fmt.Println(a)
        fmt.Printf("len(a):%v cap(a):%v
    ", len(a), cap(a))
        b := make([]int , 10, 15)
        fmt.Printf("len(b):%v cap(b):%v
    ", len(b), cap(b))
    }
    

    切片的本质

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

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

    切片s2 := a[3:6]s2下标上限cap(a)=len(a)=8相应示意图如下:slice_02

    切片的操作

    slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。

    img

    package main
    
    import "fmt"
    
    func main() {
        // 初始化数组
    	months := [...]string{1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"}
    
    	Q2 := months[4:7]
    	summer := months[6:9]
    
    	fmt.Println(Q2)     // ["April" "May" "June"]
    	fmt.Println(summer) // ["June" "July" "August"]
    
    	for _, s := range summer {
    		for _, q := range Q2 {
    			if s == q {
    				fmt.Printf("%s appears in both
    ", s)
    			}
    		}
    	}
    }
    
    切片的长度和容量

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

    package main
    
    import "fmt"
    
    func main() {
        // 初始化切片
        var a = []int{1,2}
        fmt.Printf("len(s):%v cap(s):%v
    ", len(a), cap(a))
    }
    
    判断切片是否为空

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

    package main
    
    import "fmt"
    
    func main() {
        // 初始化切片
        var a = []int{1,2}
        if len(a) == 0 {
            fmt.Println("切片为空")
        } else {
            fmt.Println("切片元素非空")
        }
    }
    
    切片元素比较

    ​ 切片之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较

    func equal(x, y []string) bool {
        if len(x) != len(y) {
            return false
        }
        for i := range x {
            if x[i] != y[i] {
                return false
            }
        }
        return true
    }
    
    赋值拷贝
    // 拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
    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]
    }
    
    遍历
    package main
    import "fmt"
    
    func main() {
    	s := []int{1, 3, 5}
    
    	for i := 0; i < len(s); i++ {
    		fmt.Println(i, s[i])
    	}
    
    	for index, value := range s {
    		fmt.Println(index, value)
    	}
    }
    
    切片添加元素

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

    package main
    import "fmt"
    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}  
        // 追加另一个切片s2到s
    	s = append(s, s2...)    // [1 2 3 4 5 6 7]
    }
    

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

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

    package main
    import "fmt"
    
    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
    ", numSlice, len(numSlice), cap(numSlice), numSlice)
    	}
    }
    
    删除切片的元素
    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:]...)

    切片修改元素值
    func main() {
    	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]
    }
    // 由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
    
    切片的复制

    内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中

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

    示例:

    func main() {
    	// copy()复制切片
    	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]
    }
    
    切片的扩容策略

    通过查看$GOROOT/src/runtime/slice.go源码

    image-20200704101219877

    成倍自动扩容切片容量数量

  • 相关阅读:
    复杂业务
    重析业务逻辑架构模式
    Katana介绍以及使用
    使用ServiceStack构建Web服务
    ASP.NET vNext 在 Mac OS
    用户端的防腐层作用及设计
    Mvc 模块化开发
    编程语言
    页面生命周期
    If you pay peanuts,you get monkeys
  • 原文地址:https://www.cnblogs.com/binliubiao/p/14488137.html
Copyright © 2011-2022 走看看