第八章: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<L ;i++{
        fmt.Printf("%d\t",s[i])
    }
    fmt.Println("");
    // 使用for-range 结构遍历切片
    for _,v := range s{
        fmt.Printf("%d\t",v)
    }
}

$ 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()实现切片复制的目标切片和源切片必须是具有相同的元素类型
  • 长度相等的两个切片复制,源切片的元素会全部复制给目标切片,此时目标切片的内容和源切片完全一致
  • 元素长度不等的两个切片进行复制,复制的元素个数以小的切片为准
  • 如果目标切片长度大于原切片长度,那么源切片的所有元素,都复制给目标切片索引相同的位置
  • 如果目标切片长度小于源切片长度,那么目标切片相同索引位置的元素内容,被源切片相同索引位置的内容覆盖
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容