第八章:Go语言切片slice

golang-gopher.png

1. 关于切片的概述

切片slice是一个可变长度的序列,切片中的每个元素都是相同类型,我们可以将切片看做动态数组

  • 切片是引用类型,是对数组的引用
  • 切片的大小是动态分配的
  • 切片的遍历和访问切片元素操作和数组类似
package main

import "fmt"

func main(){
    var demo = []int{1,2,3,4,5,6,7}
    fmt.Println(len(demo),cap(demo))
    child := demo[2:4]
    fmt.Println(child,len(child),cap(child))
}

go run main.go

7 7
[3 4] 2 5

为什么是这样的呢?我们模拟一下在内存中的布局

slice.png

上图中 ptr表示地址指针, len表示长度, cap表示容量

2. 创建切片

切片的创建通常有两种方式

  1. 基于某个数组创建切片
  2. 直接创建切片
  3. 使用内置函数make()创建切片

2.1 切片的声明

var name []T

  • name 表示切片的变量名
  • T 表示切片类型对应的元素类型

切片只是声明,并且没有初始化那么切片还未分配内存,默认值是nil

package main

import "fmt"

func main(){
    //声明一个整型切片
    var nSlice []int
    //声明一个字符串切片
    var sSlice []string
    //声明一个字符串类型的空切片
    var sEmptySlice  = []string{}
    fmt.Println(nSlice,sSlice,sEmptySlice)
    // 切片长度都是0
    fmt.Println(len(nSlice),len(sSlice),len(sEmptySlice))
    // 未初始化的切片默认值是nil
    fmt.Println(nSlice == nil)
    fmt.Println(sSlice == nil)
    // 空切片也是分配的内存,默认值不是nil
    fmt.Println(sEmptySlice == nil)
}

$ go run main.go
[] [] []
0 0 0
true
true
false

2.2 基于数组创建切片

:star: 切片slice 操作符s[i:j] (0<=i<=j<=cap(s)) 创建一个新的slice ,新的slice引用了序列s中从i到j-1索引位置的所有元素,这个s可以是数组可以是指向数组的指针,可以是slice,新的slice的元素个数是j-i个,如果省略了i,那么新的slice其实索引位置是0,如果省略了j ,那么新slice结束索引位置是len(s)-1,就是 j = len(s)

package main 

import "fmt"

func main() {
    // 初始化一个数组
    var nArr  = [10]int{0,1,2,3,4,5,6,7,8,9}
    s := nArr[2:8]
    // s 是一个int类型的切片
    fmt.Printf("s 的类型是 = %T\n",s)
    fmt.Printf("s 的元素是 = %v\n",s)
    fmt.Printf("s 的元素个数是 = %d\n",len(s))
    fmt.Printf("s 的容量是 = %d\n",cap(s))
}

$ go run main.go
s 的类型是 = []int
s 的元素是 = [2 3 4 5 6 7]
s 的元素个数是 = 6
s 的容量是 = 8

2.3 直接创建

package main

import "fmt"

func main(){
    var nSlice = []int{1,2,3,4,5}
    fmt.Printf("nSlice的长度是%d,容量数%d,内容是%v",len(nSlice),cap(nSlice),nSlice)
}

$ go run main.go
nSlice的长度是5,容量数5,内容是[1 2 3 4 5]

2.4 make()创建

格式

make([]T,size,cap)

  • T : 切片类型
  • size : 切片分配的元素个数
  • cap : 切片的容量

说明 :

  1. 内置函数make()创建的切片,可以自己指定切片大小和容量 ,若是指定的切片容量就必须不小于切片的大小,否则编译报错
  2. make()创建的切片没有给元素赋值,那么就是自动采用类型默认值
  3. make()创建的切片对应的数组由make底层维护,外部不可见
package main

import "fmt"

func main(){
    var mSlice1 []string = make([]string,3,5)
    fmt.Printf("mSlice1的长度是%d,容量数%d,内容是%v\n", len(mSlice1), cap(mSlice1), mSlice1)
    var mSlice2 []string = make([]string,3,3)
    mSlice2[0] = "tom"
    mSlice2[1] = "kobe"
    mSlice2[2] = "jack"
    fmt.Printf("mSlice2的长度是%d,容量数%d,内容是%v\n", len(mSlice2), cap(mSlice2), mSlice2)
}

$ go run main.go
mSlice1的长度是3,容量数5,内容是[  ]
mSlice2的长度是3,容量数3,内容是[tom kobe jack]

2.5 基于切片创建切片

package main

import "fmt"

func main(){
    var oldS []int= make([]int,3,10)
    fmt.Printf("切片oldS的长度是%d,容量是%d,内容是 %v\n", len(oldS), cap(oldS), oldS)
    var newS = oldS[:6]
    fmt.Printf("切片newS的长度是%d,容量是%d,内容是 %v\n", len(newS), cap(newS), newS)
}

$ go run main.go
切片oldS的长度是3,容量是10,内容是 [0 0 0]
切片newS的长度是6,容量是10,内容是 [0 0 0 0 0 0]

3. 对切片的操作

3.1 对切片添加元素

Go语言的内置函数append() 可以为切片动态添加元素,切片指向一片内存空间,可以空间能容纳一定数量的元素,当空间不能容纳足够多的元素时,切片就会进行扩容,这个操作一般发生在append()函数调用的时候

当切片扩容扩容的时候切片的容量按2的倍数扩充

  • len()函数是参看切片拥有的元素个数
  • cap()函数是查看切片容量的情况,"扩容"是容量的增扩
package main

import "fmt"

func printSlice(n []int){
    // 观察slice 的长度和容量变化
    fmt.Printf("切片n的长度是%d,容量是%d,内存地址是 %p\n",len(n),cap(n),&n)
}
func main(){
    // 定义一个切片
    var n []int
    // 打印切片信息
    printSlice(n)
    // 给切片添加18个元素
    for i:=0;i<18;i++{
        n = append(n,i)
        printSlice(n)
    }
}
切片n的长度是0,容量是0,内存地址是 0xc000004480
切片n的长度是1,容量是1,内存地址是 0xc0000044c0
切片n的长度是2,容量是2,内存地址是 0xc0000044e0
切片n的长度是3,容量是4,内存地址是 0xc000004500
切片n的长度是4,容量是4,内存地址是 0xc000004520
切片n的长度是5,容量是8,内存地址是 0xc000004540
切片n的长度是6,容量是8,内存地址是 0xc000004560
切片n的长度是7,容量是8,内存地址是 0xc000004580
切片n的长度是8,容量是8,内存地址是 0xc0000045a0
切片n的长度是9,容量是16,内存地址是 0xc0000045c0
切片n的长度是10,容量是16,内存地址是 0xc0000045e0
切片n的长度是11,容量是16,内存地址是 0xc000004600
切片n的长度是12,容量是16,内存地址是 0xc000004620
切片n的长度是13,容量是16,内存地址是 0xc000004640
切片n的长度是14,容量是16,内存地址是 0xc000004660
切片n的长度是15,容量是16,内存地址是 0xc000004680
切片n的长度是16,容量是16,内存地址是 0xc0000046a0
切片n的长度是17,容量是32,内存地址是 0xc0000046c0
切片n的长度是18,容量是32,内存地址是 0xc0000046e0

append()方法可以一次性添加多个元素到切片中

package main

import "fmt"

func main(){
    var s []string = []string{"a"}
    fmt.Printf("切片n的长度是%d,容量是%d,内容是 %v\n",len(s),cap(s),s)
    s  =append(s,"b","c","d","e")
    fmt.Printf("切片n的长度是%d,容量是%d,内容是 %v\n",len(s),cap(s),s)
}

$ go run main.go
切片n的长度是1,容量是1,内容是 [a]
切片n的长度是5,容量是5,内容是 [a b c d e]

append()可以将同类的切片追加到另一个切片后面

package main

import "fmt"

func main(){
    var s []string = []string{"a","b","c"}
    fmt.Printf("切片n的长度是%d,容量是%d,内容是 %v\n", len(s), cap(s), s)
    var s1 []string = []string{"w","x","y","z"}
    //注意这个写法,要是直接写s1报错 `cannot use s1 (type []string) as type string in append`
    s = append(s,s1...)
    fmt.Printf("切片n的长度是%d,容量是%d,内容是 %v\n", len(s), cap(s), s)

}

$ go run main.go
切片s的长度是3,容量是3,内容是 [a b c]
切片s的长度是7,容量是7,内容是 [a b c w x y z]

3.2 遍历切片

切片的遍历与数组的遍历一样,通常为两种方式

  1. for 循环遍历
  2. for -range 结构遍历
package main

import "fmt"

func main(){
    var arr  = [...]int{100,2000,300,40,58,6,7,8,9,0,11,12,13}
    s := arr[:9]
    L := len(s)
    // 使用for循环遍历slice
    for i:=0 ;i
$ go run main.go
100 2000    300 40  58  6   7   8   9   
100 2000    300 40  58  6   7   8   9   

3.3 删除切片中的元素

Go语言中没有专门的语法或者函数方法去删除切片中的元素,实现删除切片中的元素需要借助切片本身的 特点来实现

在Go语言中删除切片中元素,实际上就是以被删除元素为分界点,将前后两部分再重新连接起来

package main

import "fmt"

func main()  {
    // 定义一个切片
    var s = []int{100, 2000, 300, 40, 58, 6, 7, 8, 9, 0, 11, 12, 13}
    fmt.Printf("len = %d,cap = %d\n%v\n",len(s),cap(s),s)
    // 删除指定的元素
    delIndex := 2
    fmt.Println(s[:delIndex]," ",s[delIndex+1:])
    s = append(s[:delIndex],s[delIndex+1:]...)
    fmt.Printf("len = %d,cap = %d\n%v\n",len(s),cap(s),s)
}

$ go run main.go
len = 13,cap = 13
[100 2000 300 40 58 6 7 8 9 0 11 12 13]
[100 2000]   [40 58 6 7 8 9 0 11 12 13]
len = 12,cap = 13
[100 2000 40 58 6 7 8 9 0 11 12 13]

3.5 切片复制

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

copy()函数的格式

func copy(dst, src []Type) int

  • dst 是目标 切片
  • src 是源切片,就是将src的元素复制到dst切片中
  • 执行该函数后返回的是实际发生复制的元素个数
package main

import "fmt"

func main(){
    var s1 []int = []int{1,2,3,4,5}
    var s2 []int = []int{7,8,9}
    var s3 []string = []string{"a","b","c","d","e"}
    var s4 []string = []string{"x","y"}
    var s5 []string = []string{"C","java","javascript","golang"}
    var s6 []string = []string{"C语言","java语言","javascript语言","Go语言"}
    n1 := copy(s2,s1)
    n2 := copy(s3,s4)
    n3 := copy(s5,s6)
    fmt.Printf("复制的元素个数%d,s2 = %v\n",n1,s2)
    fmt.Printf("复制的元素个数%d,s3 = %v\n",n2,s3)
    fmt.Printf("复制的元素个数%d,s5 = %v\n",n3,s5)

}

$ go run main.go
复制的元素个数3,s2 = [1 2 3]
复制的元素个数2,s3 = [x y c d e]
复制的元素个数4,s5 = [C语言 java语言 javascript语言 Go语言]

切片复制总结 :

  • 使用copy()实现切片复制的目标切片和源切片必须是具有相同的元素类型
  • 长度相等的两个切片复制,源切片的元素会全部复制给目标切片,此时目标切片的内容和源切片完全一致
  • 元素长度不等的两个切片进行复制,复制的元素个数以小的切片为准
  • 如果目标切片长度大于原切片长度,那么源切片的所有元素,都复制给目标切片索引相同的位置
  • 如果目标切片长度小于源切片长度,那么目标切片相同索引位置的元素内容,被源切片相同索引位置的内容覆盖

你可能感兴趣的:(第八章:Go语言切片slice)