Go笔记:切片

Posted 无虑的小猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go笔记:切片相关的知识,希望对你有一定的参考价值。

  切片是对数组的拓展,在Go中数组的长度一旦定义无法被修改,切片的长度是不固定的,可以理解为切片是一个可变长度数组,是一个有相同类型元素的可变长度序列。

1、声明切片

1.1、显示声明切片

1、语法

  声明切片语法如下:

var sclicename []type

2、示例代码

 package main
 
 import "fmt"
 
 // 方式一:声明/定义切片
 func defineSlice() 
    var slice01 []string
    var slice02 []int
    var arr01 [3]int
 
    fmt.Printf("slice01: %v, 长度:%v, 容量:%v\\n", slice01, len(slice01), cap(slice01))
    fmt.Printf("slice02: %v, 长度:%v, 容量:%v\\n", slice02, len(slice02), cap(slice02))
    fmt.Printf("arr01: %v\\n, 长度:%v, 容量:%v\\n", arr01, len(arr01), cap(arr01))
 
 
 func main() 
    defineSlice()
 

  声明的切片(slice),默认切片的长度与容量为0。定义基本类型的切片,不会用其默认值填充切片。因为切片的长度是可变的,不像数组指定了长度,可以用基本类型的默认值填充。上述示例代码执行结果如下:

  

1.2、make() 函数创建切片

1、语法

  make()函数可以用来初始化 slice(切片)、Map。t 表示切片类型。

func make(t Type, size ...IntegerType) Type
  创建容量与数组长度相同的切片:
make([]type, length)

  创建容量与数组长度不同的切片:

make([]type, length, capacity)

  type:切片的类型

  length:数组的长度,切片的初始长度

  capacity:切片的容量值

2、示例代码

make函数创建的切片可以指定切片的数组长度len与容量capacity。

  当指定切片数组长度len与容量capacity为0时,与显示声明切片等价;

  当指定数组长度len与容量capacity相同并且大于0时,切片等价于数组,此时切片会对基本类型的切片设置默认值;

  当指定的数组长度len小于容量capacity时,基本类型切片会在数组长度部分设置基本类型的默认值。

 package main
 
 import "fmt"
 
 // 方式二:通过make声明切片
 func makeSlice() 
 
    // 等价于 var sliceEmpty []int
    var sliceEmpty = make([]int, 0)
    fmt.Printf("sliceEmpty: %v, 长度:%v, 容量:%v\\n", sliceEmpty, len(sliceEmpty), cap(sliceEmpty))
 
    // 指定长度的与容量的切片
    var slice01 = make([]string, 2)
    fmt.Printf("slice01: %v, 长度:%v, 容量:%v\\n", slice01, len(slice01), cap(slice01))
 
    // 指定长度的与容量的切片 省略类型, 需要用 =
    var slice02 = make([]int, 3)
    fmt.Printf("slice02: 初始值:%v, 长度:%v, 容量:%v\\n", slice02, len(slice02), cap(slice02))
 
    // 指定长度的与容量的切片 类型推导, 需要用 :=
    slice03 := make([]bool, 2)
    fmt.Printf("slice03: 初始值:%v, 长度:%v, 容量:%v\\n", slice03, len(slice03), cap(slice03))
 
    // 创建指定容量的切片,capacity为 容量值;length为数组长度,也是切片的初始长度 -> make([]T, length, capacity)
    slice04 := make([]bool, 3, 4)
    fmt.Printf("slice04: 初始值:%v, 长度:%v, 容量%v\\n", slice04, len(slice04), cap(slice04))
 
    // 创建指定容量的切片,capacity为 容量值;length为数组长度,也是切片的初始长度 -> make([]T, length, capacity)
    slice05 := make([]bool, 0, 5)
    fmt.Printf("slice05: 初始值:%v, 长度:%v, 容量%v\\n", slice05, len(slice05), cap(slice05))
 
 
 func main() 
    makeSlice()
 

  上述代码执行结果如下:

 

2、初始化切片

1、直接初始化

  声明切片的同时,初始化切片中的元素。

package main

import "fmt"

// 初始化切片方式一:直接初始化
func dirctInitSlice() 
   var slice01 = []int4, 5, 6
   fmt.Printf("slice01: %v, 长度:%v, 容量%v\\n", slice01, len(slice01), cap(slice01))
   slice02 := []string"hello", " ", "world"
   fmt.Printf("slice02: %v, 长度:%v, 容量%v\\n", slice02, len(slice02), cap(slice02))


func main() 
   dirctInitSlice()

  执行结果如下:

2、切片表达式初始化 切片

  切片的底层是数组,基于数组可通过切片表达式得到切片。切片表达式中low和high表示一个索引范围(左包含,右不包含),得到的切片长度=high-low,容量等于得到的切片的底层数组容量。
 package main
 
 import "fmt"
 
 // 初始化切片方式二:切片表达式
 func arrInitSlice() 
    arr := [...]int1, 2, 3, 4, 5, 6, 7, 8
    //取索引值3 -> 6, 左包含,右不包含
    slice01 := arr[3:7]
    fmt.Printf("slice01: %v, 长度:%v, 容量%v\\n", slice01, len(slice01), cap(slice01))
    // 取索引值 0 -> 5
    slice02 := arr[:6]
    fmt.Printf("slice02: %v, 长度:%v, 容量%v\\n", slice02, len(slice02), cap(slice02))
    // 取索引值 2 -> 7
    slice03 := arr[2:]
    fmt.Printf("slice03: %v, 长度:%v, 容量%v\\n", slice03, len(slice03), cap(slice03))
    // 取出数组中的所有元素
    slice04 := arr[:]
    fmt.Printf("slice04: %v, 长度:%v, 容量%v\\n", slice04, len(slice04), cap(slice04))
 
 
 func main() 
    arrInitSlice()
 

  执行结果如下:

 

3、访问切片

1、切片的容量与长度

从上面的示例中已经知道

  切片的容量函数:cap()、切片的长度函数:len()

  具体的示例代码不再展示,参考上面的示例即可。

2、空切片的判断

  在Go中 nil 表示 空,相当于Java中的null。显示声明的切片,未做任何初始化操作,切片的数组长度与容量都是0,是空切片。通过make()函数创建的切片,即使数组长度与容量都是0,也不是空切片。验证代码如下:

 package main
 
 import "fmt"
 
 // 空切片的判断
 func emptySlice() 
    var slice01 []int
    fmt.Printf("显示声明 slice01 ==> 长度:%v,容量:%v\\n", len(slice01), cap(slice01))
    if slice01 == nil 
       fmt.Printf("slice01 ==> 空切片\\n")
    
 
    slice02 := make([]int, 0)
    fmt.Printf("make创建 slice02 ==> 长度:%v,容量:%v\\n", len(slice02), cap(slice02))
    if slice02 == nil 
       fmt.Printf("slice02 ==> 空切片\\n")
    else 
       fmt.Printf("slice02 ==> 非空切片\\n")
    
 
    slice03 := make([]int, 0, 5)
    fmt.Printf("make创建 slice03 ==> 长度:%v,容量:%v\\n", len(slice03), cap(slice03))
    if slice03 == nil 
       fmt.Printf("slice03 ==> 空切片\\n")
    else 
       fmt.Printf("slice03 ==> 非空切片\\n")
    
 
    slice04 := make([]int, 2, 5)
    fmt.Printf("make创建 slice04 ==> 长度:%v,容量:%v\\n", len(slice04), cap(slice04))
    if slice04 == nil 
       fmt.Printf("slice04 ==> 空切片\\n")
     else 
       fmt.Printf("slice04 ==> 非空切片\\n")
    
 
 
 
 func main() 
    emptySlice()
 

  执行结果如下:

 

4、遍历切片

4.1、for

 package main
 
 import "fmt"
 
 // 遍历切片方式一:通过切片长度访问切片元素
 func visitSlice01() 
    slice01 := []boolfalse, true, true
    for i := 0; i < len(slice01); i++ 
       if slice01[i] 
          fmt.Println("Yes")
        else 
          fmt.Println("No")
       
    
 
 
 func main() 
    visitSlice01()
 

  执行结果如下:

 

4.2、for range

 package main
 
 import "fmt"
 
 // 遍历切片方式二:通过for range访问切片元素
 func visitSlice02() 
    slice01 := []string"zs", "ls", "ww", "hz"
    for i, v := range slice01 
       fmt.Printf("下标:i: %v, 元素值:v: %v\\n", i, v)
    
    fmt.Println("-----------------------------")
    for _, v := range slice01 
       fmt.Printf("元素值:v: %v\\n", v)
    
 
 
 func main() 
    visitSlice02()
 

  执行结果如下:

 

5、操作切片

  切片是动态的数组,可使用append()函数添加元素。

  切片是引用类型,通过赋值的方式会修改原有内容,可通过copy()函数拷贝切片。

5.1、添加

1、添加元素

 package main
 import "fmt"
 // 添加元素
 func addEleSlice01() 
     slice01 := []int
     slice01 = append(slice01, 1)
     slice01 = append(slice01, 2)
     slice01 = append(slice01, 3)
     slice01 = append(slice01, 4, 5, 6)
     fmt.Printf("slice01: %v\\n", slice01)
 
 func main() 
     addEleSlice01()
 

  执行结果如下:

2、添加切片

 package main
 
 import "fmt"
 
 // 添加切片
 func addSliceToSlice() 
    var slice01 = []string"tom", "jack"
    var slice02 []string
    slice02 = []string"hello", "-", "world"
    slice03 := append(slice01, slice02...)
    fmt.Printf("slice03: %v\\n", slice03)
    slice04 := append(slice01[:1], slice02[1:]...)
    fmt.Printf("slice04: %v\\n", slice04)
 
 
 func main() 
    addSliceToSlice()
 

  执行结果如下:

  

5.2、删除元素

  Go未提供删除切片元素的方法,但可以利用切片表达式删除元素。例如:从切片slice中删除索引为index的元素,slice = append(slice[:index], slice[index+1:]...)

 package main
 
 import "fmt"
 
 // 删除元素,利用切片表达式
 func delEleSlice() 
    slice01 := []int1, 2, 3, 4, 5, 6
    // 删除索引值为3的元素
    fmt.Printf("slice01[3]: %v\\n", slice01[3])
    slice02 := append(slice01[:3], slice01[4:]...)
    fmt.Printf("slice02: %v\\n", slice02)
 
 
 func main() 
    delEleSlice()
 

  执行结果如下:

  

5.3、更新元素

 package main
 
 import "fmt"
 
 // 更新执行下标的切片元素
 func updEleSlice() 
    slice01 := []int1, 2, 3
    fmt.Printf("upd before ==> slice01: %v\\n", slice01)
    slice01[1] = 200
    fmt.Printf("upd after ==> slice01: %v\\n", slice01)
 
 
 func main() 
    updEleSlice()
 

   执行结果如下:

  

6、切片的拷贝

切片是引用类型,copy()函数是一个深拷贝,切片在使用copy函数时,需要用make函数创建申请内存。

  浅拷贝:将切片A的引用赋给切片B,当切片A元素值改变时,会影响切片B。

  深拷贝:将切片A的元素值拷贝到切片B的内存中,当切片A元素值改变时,不会影响切片B。

 package main
 
 import "fmt"
 
 // copy()函数,深拷贝
 func copySlice() 
    slice01 := []int1, 2, 3
    // 1、浅拷贝
    slice02 := slice01
    // 2、深拷贝
    // 未分配内存地址,采用此种方式声明slice03切片,copy(slice03, slice01)无效
    var slice03 = []int
    // 分配内存地址
    slice04 := make([]int, 3)
    copy(slice03, slice01)
    copy(slice04, slice01)
 
    slice01[1] = 200
    fmt.Printf("原切片:slice01: %v\\n", slice01)
    fmt.Printf("`:=`赋值切片 :slice02: %v\\n", slice02)
    fmt.Printf("显示声明切片:slice03: %v\\n", slice03)
    fmt.Printf("make()创建指定长度与容量切片:slice04: %v\\n", slice04)
 
 
 func main() 
    copySlice()
 

  执行结果如下:

  
 

《Go语言精进之路》读书笔记 | 了解切片实现原理并高效使用

书籍来源:《Go语言精进之路:从新手到高手的编程思想、方法和技巧》

一边学习一边整理读书笔记,并与大家分享,侵权即删,谢谢支持!

附上汇总贴:《Go语言精进之路》读书笔记 | 汇总_COCOgsta的博客-CSDN博客


slice,中文多译为切片,是Go语言在数组之上提供的一个重要的抽象数据类型。在Go语言中,对于绝大多数需要使用数组的场合,切片实现了完美替代。并且可提供更灵活、更高效的数据序列访问接口。

13.1 切片究竟是什么

切片之于数组就像是文件描述符之于文件。在Go语言中,数组更多是“退居幕后”,承担的是底层存储空间的角色;而切片则走向“前台”,为底层的存储(数组)打开了一个访问的“窗口”(见图13-1)。

图13-1 切片打开了访问底层数组的“窗口”

可以称切片是数组的“描述符”。切切片这个描述符是固定大小的,无论底层的数组元素类型有多大。

可以用下面的语句创建一个切片实例s:

s := make([]byte, 5)
复制代码

图13-2展示了切片s在运行时层面的内部表示。

图13-2 切片运行时表示(新切片)

编译器会自动为切片建立一个底层数组,如果没有在make中指定cap参数,那么cap = len,即编译器建立的数组长度为len。

通过语法u[low: high]创建对已存在数组进行操作的切片,这被称为数组的切片化(slicing):

u := [10]byte11, 12, 13, 14, 15, 16, 17, 18, 19, 20
s := u[3:7]
复制代码

图13-3展示了切片s的内部。

图13-3 切片运行时表示(以已有数组为底层存储的切片)

切片的容量值(cap)取决于底层数组的长度。从切片s的第一个元素s[0],即u[3]到数组末尾一共有7个存储元素的槽位,因此切片s的cap为7。

还可以通过语法s[low: high]基于已有切片创建新的切片,这被称为切片的reslicing,如图13-5所示。新创建的切片与原切片同样是共享底层数组的,并且通过新切片对数组的修改也会反映到原切片中。

图13-5 切片运行时表示(基于切片s1建立新切片s2)

13.2 切片的高级特性:动态扩容

Go切片还支持一个重要的高级特性:动态扩容。零值切片可以通过append预定义函数进行元素赋值操作:

var s []byte // s被赋予零值nil
s = append(s, 1)
复制代码

我们打印出每次append操作后切片s的len和cap值:

// chapter3/sources/slice_append.go
var s []int  // s被赋予零值nil
s = append(s, 11)
fmt.Println(len(s), cap(s)) //1 1
s = append(s, 12)
fmt.Println(len(s), cap(s)) //2 2
s = append(s, 13)
fmt.Println(len(s), cap(s)) //3 4
s = append(s, 14)
fmt.Println(len(s), cap(s)) //4 4
s = append(s, 15)
fmt.Println(len(s), cap(s)) //5 8
复制代码

我们看到切片s的len值是线性增长的,但cap值却呈现出不规则的变化。通过图13-6我们更容易看清楚多次append操作究竟是如何让切片进行动态扩容的。

图13-6 切片的动态扩容

我们看到append会根据切片对底层数组容量的需求对底层数组进行动态调整。

在当前底层数组容量无法满足的情况下,按一定算法动态分配新的数组。新数组建立后,append会把旧数组中的数据复制到新数组中,成为切片的底层数组,旧数组后续会被垃圾回收掉。

13.3 尽量使用cap参数创建切片

从append的原理中可以看到重新分配底层数组并复制元素的操作代价还是挺大的,一种有效的方法是根据切片的使用场景对切片的容量规模进行预估,并在创建新切片时将预估出的切片容量数据以cap参数的形式传递给内置函数make:

s := make([]T, len, cap)
复制代码

以上是关于Go笔记:切片的主要内容,如果未能解决你的问题,请参考以下文章

Go语言学习笔记十一: 切片(slice)

go语言学习笔记 — 基础 — 高级数据类型 — 数据容器 — 切片:切片内部结构

go语言学习笔记 — 基础 — 高级数据类型 — 数据容器 — 切片:访问切片元素和子切片

《Go语言精进之路》读书笔记 | 了解切片实现原理并高效使用

go语言学习笔记 — 基础 — 高级数据类型 — 数据容器 — 切片:切片的初始化声明

go语言学习笔记 — 基础 — 高级数据类型 — 数据容器 — 切片:数组 vs. 切⽚