切片 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] |
注意事项
- 索引边界检查:
开始位置和结束位置必须在合法范围内,否则会触发运行时错误。例如,
slice[:len(slice)+1]
或slice[5:3]
都会报错 - 切片是引用类型: 通过切片表达式生成的子切片和原切片共享相同的底层数组,因此修改子切片会影响原切片的内容(如果未超出容量)
示例:
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)
说明
a
和b
均是预分配2个元素的切片,只是b
的内部存储空间已经分配了10个,但实际使用了2个元素,容量不会影响当前的元素个数,因此a
和b
取len
都是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倍数扩充,例如 1
、2
、4
、8
...
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 次
append
:numbers
是空切片,容量为 0。- 触发分配,分配容量为 1。
len = 1
,cap = 1
。
- 第 2 次
append
:- 当前容量为 1,但需要添加新元素,触发扩容。
- 扩容后,容量翻倍为 2。
len = 2
,cap = 2
。
- 第 3 次
append
:- 当前容量为 2,添加元素后容量不足,触发扩容。
- 扩容后,容量翻倍为 4。
len = 3
,cap = 4
。
- 第 4 次
append
:- 当前容量为 4,仍然足够。
len = 4
,cap = 4
。
- 第 5 次
append
:- 当前容量为 4,添加元素后容量不足,触发扩容。
- 扩容后,容量翻倍为 8。
len = 5
,cap = 8
。
- 第 6 到第 8 次
append
:- 当前容量为 8,足够容纳新元素。
len
依次增加,但cap
不变。
- 第 9 次
append
:- 当前容量为 8,添加元素后容量不足,触发扩容。
- 扩容后,容量翻倍为 16。
len = 9
,cap = 16
。
- 第 10 次
append
:- 当前容量为 16,足够容纳新元素。
len = 10
,cap = 16
。
第 n 次 append | 长度 (len ) | 容量 (cap ) | 扩容说明 | 指针变化 |
---|---|---|---|---|
1 | 1 | 1 | 分配容量为 1 | 变化 |
2 | 2 | 2 | 容量翻倍到 2 | 变化 |
3 | 3 | 4 | 容量翻倍到 4 | 变化 |
4 | 4 | 4 | 无需扩容 | 不变 |
5 | 5 | 8 | 容量翻倍到 8 | 变化 |
6~8 | 6~8 | 8 | 无需扩容 | 不变 |
9 | 9 | 16 | 容量翻倍到 16 | 变化 |
10 | 10 | 16 | 无需扩容 | 不变 |
添加 单个/多个/其他类型 元素
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]
}