跳到主要内容

切片 slice

说明

说明

切片是一个拥有 相同类型元素可变长度 的序列

定义方式

说明

T 代表切片元素类型,可以是整型、浮点型、布尔型、切片、map、函数等

切片的元素使用 [] 进行访问,在方括号中提供切片的索引即可访问元素,索引的范围从0开始,且不超过切片的最大容量

var name []T
  • 方式1 使用 make 创建

    slice := make([]int, 5) // 创建长度为5的切片,初始值为[0, 0, 0, 0, 0]
  • 方式2 直接初始化

    slice := []int{1, 2, 3} // 创建切片并初始化
  • 方式3 从现有数组中创建切片

    arr := [5]int{1, 2, 3, 4, 5}
    slice := arr[1:4] // 从数组的第2到第4个元素生成切片

使用示例

从数组或切片生成新的切片

切片默认指向一段连续内存区域,可以是数组,也可以是切片本身

从连续内存区域生成切片是常见的操作,格式如下

说明
  • slice 表示目标切片对象
  • 开始位置对应目标切片对象的索引
  • 结束位置对应目标切片的结束索引
slice [开始位置:结束位置]

从数组生成切片

var a = [3]int{1, 2, 3}
fmt.Println(a, a[1:2])

输出

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

从指定范围中生成切片

缺省开始位置 slice[:end]

  • 表示从切片的起始位置到指定的结束位置(不含结束位置)
  • 等价于 slice[0:end]
slice := []int{10, 20, 30, 40, 50}

// 缺省开始位置
subSlice := slice[:3] // 等价于 slice[0:3]
fmt.Println(subSlice) // 输出:[10 20 30]

缺省结束位置 slice[start:]

  • 表示从指定的开始位置到切片的末尾
  • 等价于 slice[start:len(slice)]
slice := []int{10, 20, 30, 40, 50}

// 缺省结束位置
subSlice := slice[2:] // 等价于 slice[2:len(slice)]
fmt.Println(subSlice) // 输出:[30 40 50]

开始和结束都缺省 slice[:]

  • 表示整个切片的所有元素
  • 等价于 slice[0:len(slice)]
slice := []int{10, 20, 30, 40, 50}

// 开始和结束都缺省
subSlice := slice[:] // 等价于 slice[0:len(slice)]
fmt.Println(subSlice) // 输出:[10 20 30 40 50]

总结对比

表达式意义示例结果
slice[:end]从起始位置到 end(不含 end[10 20 30]
slice[start:]start 到末尾[30 40 50]
slice[:]整个切片[10 20 30 40 50]

注意事项

  1. 索引边界检查: 开始位置和结束位置必须在合法范围内,否则会触发运行时错误。例如,slice[:len(slice)+1]slice[5:3] 都会报错
  2. 切片是引用类型: 通过切片表达式生成的子切片和原切片共享相同的底层数组,因此修改子切片会影响原切片的内容(如果未超出容量)

示例

slice := []int{10, 20, 30, 40, 50}
subSlice := slice[:3]
subSlice[0] = 100
fmt.Println(slice) // 输出:[100 20 30 40 50]
fmt.Println(subSlice) // 输出:[100 20 30]

重制切片,清空拥有的元素

slice := []int{10, 20, 30, 40, 50}
fmt.Println(slice[0:0]) // 输出:[]

使用 make() 函数构造切片

如果需要动态的创建一个切片,可以使用 make() 函数,格式如下

说明
  • T:切片的元素类型
  • size:为这个类型分配的元素个数
  • cap:预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题
make ( []T, size, cap)
说明
  • ab 均是预分配2个元素的切片,只是 b 的内部存储空间已经分配了10个,但实际使用了2个元素,容量不会影响当前的元素个数,因此 ablen 都是2
  • 使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作
a := make([]int, 2)
b := make([]int, 2, 10)

fmt.Println(a, b) // 输出[0 0] [0 0]
fmt.Println(len(a), len(b)) // 输出2 2

使用 append() 函数为切片增加元素

go语言的内建函数 append() 可以为切片动态添加元素,每个切片会指向一片内存空间,这片空间能容纳一定数量的元素,当空间不能容纳足够多的元素时,切片就回进行扩容,扩容往往发生在 append() 函数调用时

切片在扩容时,容量的扩展规律按容量的2倍数扩充,例如 1248 ...

var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
}

输出

len: 1  cap: 1  pointer: 0x14000104020
len: 2 cap: 2 pointer: 0x14000104040
len: 3 cap: 4 pointer: 0x1400012e020
len: 4 cap: 4 pointer: 0x1400012e020
len: 5 cap: 8 pointer: 0x1400011a080
len: 6 cap: 8 pointer: 0x1400011a080
len: 7 cap: 8 pointer: 0x1400011a080
len: 8 cap: 8 pointer: 0x1400011a080
len: 9 cap: 16 pointer: 0x1400012c080
len: 10 cap: 16 pointer: 0x1400012c080

逐步分析

  1. 第 1 次 append
    • numbers 是空切片,容量为 0。
    • 触发分配,分配容量为 1。
    • len = 1, cap = 1
  2. 第 2 次 append
    • 当前容量为 1,但需要添加新元素,触发扩容。
    • 扩容后,容量翻倍为 2。
    • len = 2, cap = 2
  3. 第 3 次 append
    • 当前容量为 2,添加元素后容量不足,触发扩容。
    • 扩容后,容量翻倍为 4。
    • len = 3, cap = 4
  4. 第 4 次 append
    • 当前容量为 4,仍然足够。
    • len = 4, cap = 4
  5. 第 5 次 append
    • 当前容量为 4,添加元素后容量不足,触发扩容。
    • 扩容后,容量翻倍为 8。
    • len = 5, cap = 8
  6. 第 6 到第 8 次 append
    • 当前容量为 8,足够容纳新元素。
    • len 依次增加,但 cap 不变。
  7. 第 9 次 append
    • 当前容量为 8,添加元素后容量不足,触发扩容。
    • 扩容后,容量翻倍为 16。
    • len = 9, cap = 16
  8. 第 10 次 append
    • 当前容量为 16,足够容纳新元素。
    • len = 10, cap = 16
第 n 次 append长度 (len)容量 (cap)扩容说明指针变化
111分配容量为 1变化
222容量翻倍到 2变化
334容量翻倍到 4变化
444无需扩容不变
558容量翻倍到 8变化
6~86~88无需扩容不变
9916容量翻倍到 16变化
101016无需扩容不变

添加 单个/多个/其他类型 元素

package main

import "fmt"

func main() {
var car []string

// 添加一个元素
car = append(car, "aaa")

// 添加多个元素
car = append(car, "bbb", "ccc", "ddd")

// 添加切片
team := []string{"AAA", "BBB"}
car = append(car, team...) // 在team后边加上了... 表示将team整个添加到car的后面

fmt.Println(car)
}

从切片中删除元素

package main

import "fmt"

func main() {
s := []int{1, 2, 3, 4, 5, 6}

// 指定删除位置
index := 2

// 查看删除位置之前的元素和之后的元素
fmt.Println(s[:index], s[index+1:]) // 输出 [1 2] [4 5 6]

// 将删除点前后的元素连接起来
s = append(s[:index], s[index+1:]...) // 在 s[index+1:] 后边加上了... 表示将 s[index+1:] 整个添加到 s[:index] 的后面

fmt.Println(s) // 输出 [1 2 4 5 6]
}

切片合并

package main

import "fmt"

func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
fmt.Println(s1, s2) // 输出 [1 2 3] [4 5 6]

s1 = append(s1, s2...) // 在 s2 后边加上了... 表示将 s2 整个添加到 s1的后面
fmt.Println(s1) // 输出 [1 2 3 4 5 6]
}
Right Bottom Gif
Right Top GIF