美文网首页
Go基础-006-02 复合数据类型 切片

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

作者: 如逆水行舟不进则退 | 来源:发表于2020-02-23 10:39 被阅读0次

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]

相关文章

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

    1.概述 自动扩容的数组。扩容操作,主要体现在 append() 上。 2.类型 相对于数组,切片可以看做是容量可...

  • <>

    第六章重点: go语言数据类型:基础类型(数字,字符串,布尔类型),复合类型(数组,结构体),引用类型(指针,切片...

  • Go 专栏|复合数据类型:数组和切片 slice

    原文链接: Go 专栏|复合数据类型:数组和切片 slice[https://mp.weixin.qq.com/s...

  • Go学习-数据类型

    Go数据类型 Go语言将数据类型分为四类 基础类型 复合类型 引用类型 接口类型 基础类型 数字 字符串 布尔 整...

  • Go语言第3天 - 常用数据类型

    以下内容部分参考自Go语言基础数据类型Go语言中的数组切片:特立独行的可变数组Go语言数据类型-数组Go标准容器之...

  • Go核心语言编程 - 复合数据类型(3)

    Go对应的复合数据类型有数组,字典,指针,切片,通道,结构和结构,他们字面类型如下: • 指针 • 数组• 数组初...

  • 第三章 基础数据类型

    数据类型分类 基础类型 数字 字符串 布尔型 复合类型 数组 结构体 引用类型 指针 切片 字典 函数 通道 接口...

  • 4.Golang数据类型

    Go 语言数据类型包含基础类型和复合类型两大类。基础数据类型包括:布尔型、整型、浮点型、复数型、字符型、字符串型、...

  • 《GO语言圣经》读书笔记 第三章 基础数据类型

    Go语言将数据类型分为四类:** 基础类型、复合类型、引用类型和接口类型 ** 整型 Go语言的数值类型包括几种不...

  • 04 Golang数据类型

    分类 Go语言中的数据类型分为:基本数据类型和复合数据类型 基本数据类型 整型、浮点型、布尔型、字符串 复合数据类...

网友评论

      本文标题:Go基础-006-02 复合数据类型 切片

      本文链接:https://www.haomeiwen.com/subject/cxxdqhtx.html