Go 语言学习之 slice

时间:2022-07-22
本文章向大家介绍Go 语言学习之 slice,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

01

概念

slice 是有 3 个字段的数据结构,这些数据结构包含 Go 语言需要操作底层数组的元数据,这 3 个字段分别是指向底层数组的指针,切片的长度和切片的容量。

指针指向数组的第一个可以从 slice 中访问的元素,这个元素并不一定是数组的第一个元素,长度是指 slice 中元素的个数,它不能超过 slice 的容量。容量的大小通常是从 slice 的起始元素到底层数组的最后一个元素间元素的个数。Go 的内置函数 len 和 cap 用来返回 slice 的长度和容量。

在 Go 语言中,访问切片元素和数组类似,通过下标访问切片的指定元素的值,需要注意的是,切片之间不可以比较,只可以判断切片是否为 nil。

通过代码,我们演示 slice 与底层数组的关联。

func main() {
  arr := [...]int{10, 11, 12, 13, 14}
  slice := arr[1:4]
  fmt.Printf("slice 的长度:%d,slice 的容量:%dn", len(slice), cap(slice))
  fmt.Println("slice = ", slice)
  fmt.Printf("slice 的内存地址:%pn", slice)
  fmt.Printf("arr[1] 的内存地址:%pn", slice)
}

02

创建切片

  • 内置函数 make 创建切片

创建 slice 可以使用内置函数 make,使用 make 创建 slice,需要传入三个参数,第一个参数传入 slice 的数据类型,第二个参数传入 slice 的长度,第三个参数是可选参数,传入 slice 的容量,如果第三个参数不传,slice 的容量和长度相等,需要注意的是,slice 的容量小于长度会在编译时报错;

func main() {
  s1 := make([]int, 2, 4)
  fmt.Printf("s1 的长度:%d,容量:%dn", len(s1), cap(s1))
  s2 := make([]int, 2)
  fmt.Printf("s2 的长度:%d,容量:%dn", len(s2), cap(s2))
}
  • 使用 slice 字面量声明切片

也可以通过 slice 字面量声明 slice,这种创建 slice 的方式类似创建数组,只是不需要指定 [] 运算符里的值,初始的长度和容量会基于初始化时提供的元素的个数确定,使用 slice 字面量创建 slice 时,可以通过在初始化时给出所需的长度和容量作为索引,设置 slice 初始长度和容量。通过代码,演示两种创建 slice 的方式。

func main() {
  s3 := []int{0, 1, 2, 3}
  fmt.Printf("s3 的长度:%d,容量:%dn", len(s3), cap(s3))
  s4 := []int{9: 9}
  fmt.Printf("s4 的长度:%d,容量:%dn", len(s4), cap(s4))
}
  • 创建 nil 切片

在 Go 语言中,nil 切片是很常见的创建切片的方法,nil 切片可以用于很多标准库和内置函数,nil 切片用来描述一个不存在的切片,例如,函数要求返回一个切片,但是发生异常的时候。创建 nil 切片,只要在声明切片时不做任何初始化,就会创建一个 nil 切片。通过代码,我们演示创建 nil 切片。

func main() {
  var sliceNil []int
  fmt.Printf("sliceNil 的长度:%d,容量:%d,类型:%Tn", len(sliceNil), cap(sliceNil), sliceNil)
  fmt.Println("sliceNil = ", sliceNil)
}
  • 创建空切片

在 Go 语言中,空切片在底层数组包含 0 个元素,也没有分配任何内存空间,空切片可用来表示空集合,例如,数据库查询返回 0 个查询结果时,通过代码,我们演示创建空切片。

func main() {
  s5 := make([]int, 0)
  fmt.Printf("s5 的长度:%d,容量:%dn", len(s5), cap(s5))

  s6 := []int{}
  fmt.Printf("s6 的长度:%d,容量:%dn", len(s6), cap(s6))
}
  • 基于数组或数组指针创建切片 详见文章开头 01
  • 基于切片创建新切片

在 Go 语言中,可以基于切片创建新的切片,切片和新切片的底层数组相同,切片可以访问整个底层数组,新切片不可以访问整个底层数组,修改新切片的值,切片的值同时会改变。

还可以使用第三个索引选项 slice[i:j:k] 控制新切片的容量,设置容量大于已有容量,编译时会报错。设置限制容量有个好处,可以防止不小心修改了原本不想修改的原切片的值,新切片的长度等于 j-i,容量等于 k-i。

通过代码,我们演示基于切片创建新切片。

func main() {
  s7 := []int{10, 11, 12, 13, 14, 15, 16, 17, 18, 19}
  s8 := s7[1:3]
  fmt.Printf("s7 的长度:%d,容量:%dn", len(s7), cap(s7))
  fmt.Printf("s8 的长度:%d,容量:%dn", len(s8), cap(s8))
  s8[0] = 21
  fmt.Println("s8 = ", s8)
  fmt.Println("s7 = ", s7)
}
  • 创建多维切片

在 Go 语言中,切片和数组一样,是一维的,可以组合多个切片形成多维切片。通过代码,我们演示创建多维切片。

func main() {
  s9 := [][]int{{1, 2, 3}, {11, 12, 13, 14}}
  fmt.Printf("s9 的长度:%d,容量:%dn", len(s9), cap(s9))
  fmt.Printf("s9[0] 的长度:%d,容量:%dn", len(s9[0]), cap(s9[0]))
  fmt.Printf("s9[1] 的长度:%d,容量:%dn", len(s9[1]), cap(s9[1]))
  fmt.Println(s9)
}

03

切片遍历

在 Go 语言中,除了可以使用 for 语句遍历切片之外,也可以使用 for...range 语句遍历切片,二者区别是 range 创建了每个元素的副本,而不是直接返回对该元素的引用,而且 range 总是从切片的头部开始遍历。range 会返回两个值,第一个值是索引位置,第二个值是该索引位置对应元素值的一份副本,如果不需要索引值,可以通过“_”符号忽略。通过代码,我们演示使用 range 遍历切片。

func main() {
  s10 := []int{0, 1, 2, 3, 4, 5}
  for index, value := range s10 {
    fmt.Printf("s10[%d] = %dn", index, value)
  }
}

04

内置函数 append

在 Go 语言中,切片相比数组有个好处,切片不像数组固定长度,切片的长度可以追加。

使用内置函数 append 可以追加切片的长度,当切片指向的底层数组还有额外容量可用时,append 将可用的元素合并到切片的长度,如果切片的底层数组没有足够的可用容量,append 会创建一个新的底层数组,将切片现有值复制到新底层数组,再追加新的值;append 还可以使用 “...” 追加另一个切片的所有元素到当前切片中,通过代码,我们演示内置函数 append 的用法。

func main() {
  s11 := []int{10, 11, 12, 13, 14}
  s12 := s11[1:3]
  fmt.Printf("s12 的长度:%d,容量:%d,地址:%pn", len(s12), cap(s12), s12)
  fmt.Println("s12 = ", s12)
  s12 = append(s12, 21, 22, 23)
  fmt.Printf("s12 的长度:%d,容量:%d,地址:%pn", len(s12), cap(s12), s12)
  fmt.Println("s12 = ", s12)
  
  s13 := []int{10, 11, 12}
  s14 := []int{20, 21, 22}
  fmt.Printf("s14 的长度:%d,容量:%d,地址:%pn", len(s14), cap(s14), s14)
  fmt.Println("s14 = ", s14)
  s14 = append(s14, s13...)
  fmt.Printf("s14 的长度:%d,容量:%d,地址:%pn", len(s14), cap(s14), s14)
  fmt.Println("s14 = ", s14)
}

05

内置函数 copy

在 Go 语言中,内置函数 copy 可以在两个切片之间复制数据,允许指向同一底层数组,允许目标区间重叠,最终所被复制长度以较短的切片长度为准。通过代码,我们演示内置函数 copy 的用法。

func main() {
  s15 := []int{10, 11, 12, 13, 14, 15, 16, 17, 18, 19}
  s16 := s15[4:]
  newSlice := copy(s15[:4], s16)
  fmt.Printf("newSlice = %d,s15 = %v", newSlice, s15)
}

完整代码:

输出结果:

slice 的长度:3,slice 的容量:4
slice =  [11 12 13]
slice 的内存地址:0xc00001c158
arr[1] 的内存地址:0xc00001c158
s1 的长度:2,容量:4
s2 的长度:2,容量:2
s3 的长度:4,容量:4
s4 的长度:10,容量:10
sliceNil 的长度:0,容量:0,类型:[]int
sliceNil =  []
s5 的长度:0,容量:0
s6 的长度:0,容量:0
s7 的长度:10,容量:10
s8 的长度:2,容量:9
s8 =  [21 12]
s7 =  [10 21 12 13 14 15 16 17 18 19]
s9 的长度:2,容量:2
s9[0] 的长度:3,容量:3
s9[1] 的长度:4,容量:4
[[1 2 3] [11 12 13 14]]
s10[0] = 0
s10[1] = 1
s10[2] = 2
s10[3] = 3
s10[4] = 4
s10[5] = 5
s12 的长度:2,容量:4,地址:0xc00001c188
s12 =  [11 12]
s12 的长度:5,容量:8,地址:0xc000018180
s12 =  [11 12 21 22 23]
s14 的长度:3,容量:3,地址:0xc000014240
s14 =  [20 21 22]
s14 的长度:6,容量:6,地址:0xc00001c1b0
s14 =  [20 21 22 10 11 12]
newSlice = 4,s15 = [14 15 16 17 14 15 16 17 18 19]%