从零开始学Go之容器:切片

Posted vingb2by

tags:

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

切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型。

切片的内部结构包含开始位置地址(&)、大小(len)和容量(cap)。

切片并不存储任何数据,它只是描述了底层数组中的一段。

更改切片的元素会修改其底层数组中对应的元素。

 

声明:

var 数组名 []类型

var a []int

切片的声明跟数组很相似,但是长度是不定长的,所以不需要说明长度。

 

切片的初始化与使用:

var 数组名 []类型初始化值列表

var a []int1,2,3

实际上则是创建一个相同长度的数组然后对其引用

由数组或切片生成新的切片:

切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:

a[low : high]
例子:
func main() 
 var number [50]int //声明一个长度为50的数组
?
 //从1到50下标输入
 for i := 0; i < len(number); i++ 
  number[i] = i + 1
 
?
 var s1 = number[10:20]
 s2 := number[20:25]
 s3 := s1[4:8]
?
 fmt.Println(s1, s2, s3)

运行结果

[11 12 13 14 15 16 17 18 19 20] [21 22 23 24 25] [15 16 17 18]

 

切片的默认行为:

在进行切片时,你可以利用它的默认行为来忽略上下界。

切片下界的默认值为 0,上界则是该切片的长度。

对于数组 var a [10]int 来说,以下切片是等价的:

a[0:10]
a[:10]
a[0:]
a[:]

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置-开始位置。
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取。
  • 当缺省开始位置时,表示从连续区域开头到结束位置。
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾。
  • 两者同时缺省时,与切片本身等效。
  • 两者同时为0时,等效于空切片,一般用于切片复位。

根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误。生成切片时,结束位置可以填写 len(slice) 但不会报错。

 

切片的内部结构:

切片内部的结构有开始位置的地址、长度和容量。

地址:即切片创建时指向一个底层数组元素的指针

长度:即切片内部元素数量,可以用len求得

容量:当长度大于容量时,成倍增长,可以用cap求得

例:当一个切片创建时长度为3,容量为4;则当切片长度为5时,容量为8...

 

make创建切片:

var 变量名 = make( []类型, 长度, 预分配元素数量即cap)

变量名 := make( []类型, 长度, 预分配元素数量即cap)

a := make([]int, 5)

其中容量是可选的,若没有这个参数,则make出的切片长度和容量相同

切片的空值:

切片的零值是 nil

nil 切片的长度和容量为 0 且没有底层数组

要记得切片的初始化后即使元素为空,长度和容量为零,但地址仍然指向底层数组

 var x []int
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil 
  fmt.Println("ok1")
 
 
 x = make([]int, 0)
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil 
  fmt.Println("ok2")
 
 
 x = []int
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil 
  fmt.Println("ok3")
 
 
 x = number[0:0]
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil 
  fmt.Println("ok4")
 

运行结果

[] 0 0

0x0

ok1

[] 0 0

0x55c988

[] 0 0

0x55c988

[] 0 50

0xc04207e000

 

切片元素添加append:

func append(slice []Type, elems ...Type) []Type
元素添加:
var a []int
a = append(a,1)//单元素添加
a = append(a,2,3,4)//多元素添加
a = append(a,[]int1,2,3...)//多元素添加
 
头部添加:
var a = []int1,2,3
a = append([]int0, a...) // 在开头添加1个元素
a = append([]int-3,-2,-1, a...) // 在开头添加1个切片

在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制 1 次。因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多

 
特定位置插入:

可以将多个 append 操作组合起来,实现在切片中间插入元素:

var a []int
a = append(a[:i], append([]intx, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int1,2,3, a[i:]...)...) // 在第i个位置插入切片

每个添加操作中的第二个 append 调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 。

 

切片元素复制copy:

func copy(dst, src []Type) int

第一个参数是要复制的目标 slice,第二个参数是源 slice

 
切片复制:

如果两切片相等则源slice复制所有元素到目标slice,当两切片长度不相等则复制按最小的切片长度复制元素

func main() 
 slice1 := []int1, 2, 3, 4, 5, 6
 slice2 := []int11, 12, 13
 copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
 fmt.Println("slice1:",slice1,len(slice1),cap(slice1))
 fmt.Println("slice2:",slice2,len(slice2),cap(slice2))
 slice1 = []int1, 2, 3, 4, 5, 6
 slice2 = []int11, 12, 13
 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
 fmt.Println("slice1:",slice1,len(slice1),cap(slice1))
 fmt.Println("slice2:",slice2,len(slice2),cap(slice2))

运行结果:

slice1: [1 2 3 4 5 6] 6 6

slice2: [1 2 3] 3 3

slice1: [11 12 13 4 5 6] 6 6

slice2: [11 12 13] 3 3

 

切片元素删除:

Go没有内置的切片元素删除函数,所以我们要删除切片的元素的时候仍然要使用append

 
删除切片尾部的元素:
a = []int1, 2, 3
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素
 
删除开头的元素可以直接移动数据指针:
//直接移动指针
a = []int1, 2, 3
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
//利用append不改变内存空间结构变化完成
a = []int1, 2, 3
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
//copy完成删除
a = []int1, 2, 3
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
 
对于删除中间的元素:
//append完成
a = []int1, 2, 3, ...
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
//copy完成
a = []int1, 2, 3, ...
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

 

切片遍历:

跟数组一样,有下标遍历和range遍历两种方式:

func main() 
    slice := []int1, 2, 3, 4, 5, 6
    //仿C的写法
    for i := 0; i < len(slice); i++ 
        fmt.Println(slice[i])
    

    //go中的range方法
    for k, v := range slice 
        fmt.Println(k, v)
    

运行结果

1

2

3

4

5

6

0 1

1 2

2 3

3 4

4 5

5 6

以上是关于从零开始学Go之容器:切片的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学Go之容器:列表

从零开始学Go之基本:包函数声明与格式化输出

从零开始学Go之基本语法:基本数据类型与变量

从零开始学多线程之构建快

从零开始学c++之STL——vector的介绍和使用

从零开始学 Docker-容器数据卷实战