Go基础-006-02 复合数据类型 切片

1.概述

自动扩容的数组。扩容操作,主要体现在 append() 上。

2.类型

相对于数组,切片可以看做是容量可变(长度可变)的数组。当切片预先开辟的存储空
间已满时,可以通过扩容的方案,来增加存储空间,进而存储更多的元素。

类型的定义: []T
语法上,就是没有长度限制的数组!T 可以是任意类型

代码示例:

func main() {
  var sli []int
  fmt.Printf("%T/n", sli) // []int
  fmt.Println(sli) // []
}

3.字面量

字面量支持如下:

  • []int{1,2, 3, 4},列出全部元素
  • []int{0:10, 5:60},通过索引指定部分元素 通过字面量语法,通过类型推导,得到切片类型。

代码示例:

func main() {

    s1 := []int{1,2,3,4} 
    s2 := []int{1:3, 5:3}

    fmt.Println(s1) // [1 2 3 4]
    fmt.Println(s2) // [0 3 0 0 0 3]
}

4.长度和容量,len(), cap()

对于切片来说,存在两个概念:

  • 长度,使用len()内置函数获取,表示切片中元素的个数。
  • 容量,使用cap()内置函数获取,表示切片可以支持的最多的元素个数。

其中,容量可以在使用 make 创造切片是指定,或者在为切片增加元素时通过扩容来改变。

5.make() 创建切片

语法:make(类型,长度,容量)

代码示例:

func main() {

    s1 := make([]int, 5, 10)

    fmt.Printf("%T\n", s1) // []int
    fmt.Println(s1, len(s1), cap(s1)) // [0 0 0 0 0]  5 10
}

如上构建的切片,有 10 个容量,意味着最多可以存储 10 个元素。
但当前仅仅有 5 个真实元素,就是说长度为 5。意味着最大的索引(5-1)为 4。

作用:
使用 make 构建切片的优势在于,当明确切片的容量时,可以通过 make 预先在 内存中开辟空间,避免了元素个数增加,频繁扩容的内存操作。

6.append(),追加元素

当需要在当前切片中,追加(增加)新的元素时,需要使用 append()内置函数来实现。

func main() {

    s1 := make([]int, 5, 10)

    fmt.Printf("%T\n", s1) // []int
    fmt.Println(s1, len(s1), cap(s1)) // [0 0 0 0 0]  5 10

    s2 := append(s1, 1024)
    fmt.Println(s1, len(s1), cap(s1)) // [0 0 0 0 0]  5 10
    fmt.Println(s2, len(s2), cap(s2)) // [0 0 0 0 0 1024]  6 10
}

通过结果分析,append 的返回值,是新增了元素的切片,由于原切片容量足以加入新的元素,导致新切片的容量不会改变,但是长度改变了,增加一个有效元素!

若当前容量已经用尽,len() == cap(), 此时,再次追加 append,会导致切片扩容,演示:

func main() {

    s1 := make([]int, 5, 10)

    fmt.Printf("%T\n", s1) // []int
    fmt.Println(s1, len(s1), cap(s1)) // [0 0 0 0 0]  5 10


    s3 := append(s1, 1,2,3,4,5,6)
    fmt.Println(s3, len(s3), cap(s3))  
    //输出 [0 0 0 0 0  1 2 3 4 5 6]  11 20
}

注意:扩容,不是增加几个元素,容量就增加几个。 内部会有一定的扩容算法。
大概: 当容量较小时,大概率会翻倍增加。 当容量较大时,会增加一定的数量,不是翻倍了。

7.引用类型

也叫地址类型,意味着当得到一个切片类型的变量时,该变量存储的不是切片,而是切 片值的地址,导致使用切片进行赋值传递时,多个切片变量,会同时使用同一个切片的值。

如下所示:

func main() {

    s1 := []int{1,2,3}
    s2 := s1

    fmt.Println(s1, s2) // [1 2 3] [1 2 3]
    s2[0] = 2
    fmt.Println(s1, s2) // [2 2 3] [2 2 3]
}

大概描述图:


图例中,对切片类型存储的结构圆圈,仅仅描述了地址部分,整体结构会更复杂。

8.拷贝操作,copy()

由于引用类型间的复制,不会得到新的数据,因此若需要得到原始数据的拷贝,使用copy()内置函数来实现。

语法:copy(目标切片,原始切片)
将元素从原始切片,拷贝到目标切片。原始切片和目标切片不是同一个切片。

代码演示

s1 := []int{1, 2, 3} 
s2 := make([]int, 3)
copy(s2, s1) 
fmt.Println(s1, s2)  // [1 2 3] [1 2 3]
s2[1] = 22 
fmt.Println(s1, s2) // [1 2 3] [1 22 3]

说明:
copy 的流程,就是遍历 s1(原始切片),利用索引的对应关系,依次为 s2(目标切片)赋值。 语法上要求,s2 的容量要大于或等于 s1。

9.切片结构

结构图:

如图所示,切片由,底层数组和切片上层结构组成。 底层数组存储元素数据,上层结构,记录了数组的地址,以及该切片的长度和容量。
当切片需要扩容时,意味着需要更换底层数组。得到一个更大长度的底层数组,就是切 片的扩容。

10.操作

1) 索引操作 [] : 与数组一致
2)长度 len()
3)容量 cap()
4)追加元素 append()
5)拷贝,copy()
6)基于数组做切片

语法 :
arr[start:end]

  • start, 开始索引,包含,省略表示 0。
  • end, 结束索引,不包含,省略表示到最后

代码演示:

func main() {

    arr := [...]int{1,2,3,4,5,6}
    s1 := arr[2:5]
    fmt.Println(s1, len(s1), cap(s1)) //[3 4 5] 3 4

    s2 := arr[:3]
    fmt.Println(s2, len(s2), cap(s2)) //[1 2 3] 3 6

    s3 := arr[3:]
    fmt.Println(s3, len(s3), cap(s3)) //[4 5 6] 3 3

    s4 := arr[:]
    fmt.Println(s4, len(s4), cap(s4)) //[1 2 3 4 5 6] 6 6

    s4[2] = 1024
    fmt.Println(arr, s1, s2, s3, s4)
    // [1 2 1024 4 5 6] [1024 4 5] [1 2 1024] [4 5 6] [1 2 1024 4 5 6]

}

注意:
①得到的切片,元素个数决定长度。容量是从开始元素到数组最后一个元素的数量, 作为切片的容量。

②得到的切片,所使用的底层数组为同一个。修改了任意的元素,都会对底层数组和对应 的切片带来影响。(也是切片的结构导致的)。如下图:

7)基于切片做切片

语法与基于数组做切片是一致的。 原理也是一致的。

代码演示:

func main() {

    s0 := []int{1,2,3,4,5,6}
    s1 := s0[2:5]
    fmt.Println(s1, len(s1), cap(s1)) //[3 4 5] 3 4

    s2 := s0[:3]
    fmt.Println(s2, len(s2), cap(s2)) //[1 2 3] 3 6

    s3 := s0[3:]
    fmt.Println(s3, len(s3), cap(s3)) //[4 5 6] 3 3

    s4 := s0[:]
    fmt.Println(s4, len(s4), cap(s4)) //[1 2 3 4 5 6] 6 6

    s4[2] = 1024
    fmt.Println(s0, s1, s2, s3, s4)
    // [1 2 1024 4 5 6] [1024 4 5] [1 2 1024] [4 5 6] [1 2 1024 4 5 6]
}

容量也同基于数组的切片
全部的切片也是同一个底层数组,修改一个,也会影响相关的元素。

8)... 展开切片

场景:当需要将某个切片内的元素,全部追加到另一个切片中时,语法如下:

func main() {

    s1 := []int{1,2,3}
    s2 := []int{4,5,6,7,8}
    s3 := append(s1, s2...)
    fmt.Println(s3) // [1 2 3 4 5 6 7 8]

}

s2... 就是使用 ... 展开运算符,将 s2 展开后,作为参数传递到 append 中。

9)遍历

与遍历数组是一致的。使用 for range 结构。

  • 得到索引和值
for i, v := range slice {
}
  • 仅仅得到索引
for i := range slice { 
}
  • 仅仅得到值
for _, v := range slice { 
}
10)nil 与 切片

nil,空指针,指针类型的零值。
由于切片为引用类型,当切片使用 var 定义同时未指定初始值时,分配的零值就是 nil。
可以比较 与 nil 的关系,结果为真,相等。
代码:

var s []int
fmt.Println(s, s == nil) // []  true

注意:
var s []T 与 s:=[]T{} 是不同的。

var s []T 定义的切片,使用 nil 进行零值初始化的切片,使用切片的 s 的零值 nil 初始
化。这种定义语法 意义不大。

s := []T{}, 使用字面量定义的切片,使用元素的零值进行初始化,字面量值的语法表示。
意味着已经存在这个值[]T{}在内存空间中,不再是空指针了。

代码示例:

var s []int
fmt.Println(s, s == nil) // []  true

s1 := []int{}
fmt.Println(s1, s1 == nil) // []  false 

11.练习题

1)利用切片,模拟队列结构

队列结构,先进先出结构。 支持两个操作:

  • 从左边加入元素
  • 从右边取出元素



    提示:提示,使用 append(), len(),[:]配合实现。
    答案:

// 队列
q := []int{2, 3} // 入
ele := 1
q = append([]int{ele}, q...)
fmt.Println(q) // [1 2 3]
// 出
ele = q[len(q)-1]
q = q[:len(q)-1] 
fmt.Println(ele, q) //  3 [1 2]

2)利用切片,模拟栈结构

栈结构,先进后出。 支持两个操作:

  • 从左边加入元素
  • 从左边取出元素


答案:

// 栈
q := []int{2, 3}
// 入
ele := 1
q = append([]int{ele}, q...) 
fmt.Println(q) // [1 2 3]
// 出
ele = q[0] q = q[1:]
fmt.Println(ele, q) // 1 [2 3]

3)删除指定索引的元素

给出指定的索引,将该索引删除,得到新的切片, 例如,给出 索引为 3.

答案:

// 删除元素
s := []int{1, 2, 3, 4, 5, 6}
idx := 3
s = append(s[:idx], s[idx+1:]...) 
fmt.Println(s) //  [1 2 3 5 6]

4)在指定的索引位置,插入某些元素

给出索引位置 3, 插入 10, 11, 演示:


答案:

// 插入元素
s := []int{1, 2, 3, 4, 5, 6} 
inserts := []int{10, 11}
idx := 3
rest := make([]int, len(s)-idx) 
copy(rest, s[idx:])
s = append(append(s[:idx], inserts...), rest...) 
fmt.Println(s) // [1 2 3 10 11 4 5 6]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容