go切片(slice)
100 次阅读
一、切片基础概念
1. 切片是什么
切片是基于数组封装的动态、可变长引用类型,解决数组长度固定的缺陷,是Go开发最常用的数据结构。
底层结构包含三部分:
- 指针:指向底层数组起始位置
- 长度 len:切片当前存储元素个数,
len()获取 - 容量 cap:底层数组最大容纳元素数量,
cap()获取
2. 切片与数组核心区别
| 特性 | 数组 [n]T | 切片 []T |
|---|---|---|
| 长度 | 固定,声明时确定 | 动态可变,append自动扩容 |
| 类型 | [3]int、[5]int 是两种不同类型 |
[]int 统一类型,不受长度限制 |
| 传递特性 | 值类型,完整拷贝 | 引用类型,仅拷贝头部结构体 |
| 创建方式 | 直接声明 | 数组截取、make、直接字面量 |
二、切片创建四种方式
方式1:字面量直接创建
package main
import "fmt"
func main() {
// 底层数组长度=切片长度,容量=长度
s := []int{10, 20, 30}
fmt.Println(len(s), cap(s)) // 3 3
}
方式2:从数组截取生成切片
语法:arr[start:end]
- start:起始下标(包含),默认0
- end:结束下标(不包含),默认数组长度
func main() {
arr := [5]int{1,2,3,4,5}
s1 := arr[1:3] // 下标1、2:[2,3]
s2 := arr[:3] // 0~2
s3 := arr[2:] // 2~末尾
s4 := arr[:] // 完整数组转为切片
fmt.Println(s1)
}
截取切片共享底层数组,修改切片会同步影响原数组。
方式3:make 函数创建(推荐,指定长度/容量)
语法:make([]T, len, cap)
- T:元素类型
- len:初始化元素个数,默认零值填充
- cap:底层数组容量,省略时等于len
func main() {
// len=2,cap=5
s1 := make([]int, 2, 5)
// len=3,cap=3
s2 := make([]int, 3)
fmt.Println(s1, len(s1), cap(s1)) // [0 0] 2 5
}
方式4:空切片 nil 切片
func main() {
var s []int // nil切片,len=0 cap=0,底层无数组
s2 := []int{} // 空切片,底层分配空数组,len=0 cap=0
fmt.Println(s == nil) // true
fmt.Println(s2 == nil) // false
}
三、切片基本操作
1. 读取/修改元素
通过下标访问,下标不能超过 len(s)-1,否则运行panic
s := []string{"Go", "Java"}
fmt.Println(s[0])
s[1] = "Rust"
2. 切片截取再切片
s := []int{1,2,3,4,5,6}
sub := s[1:4] // [2,3,4]
sub2 := sub[1:] // [3,4]
3. append 追加元素(核心功能)
向切片尾部追加数据,容量不足时自动扩容
func main() {
s := make([]int, 2, 3)
s[0] = 1
s[1] = 2
// 追加单个元素
s = append(s, 3)
// 容量已满,再次追加触发扩容
s = append(s, 4)
// 批量追加
s = append(s, 5, 6, 7)
fmt.Println(s)
// 切片合并 ... 打散切片
s1 := []int{1,2}
s2 := []int{3,4}
s1 = append(s1, s2...)
fmt.Println(s1)
}
扩容规则:
- 容量 < 1024:每次扩容为原来2倍
- 容量 ≥ 1024:每次扩容增长原来的1/4
4. copy 切片拷贝
copy(dst, src),按较短切片长度复制,不共享底层数组
func main() {
src := []int{1,2,3}
dst := make([]int, 2)
copy(dst, src)
fmt.Println(dst) // [1,2]
}
四、切片遍历
1. 普通for循环
s := []int{10,20,30}
for i := 0; i < len(s); i++ {
fmt.Printf("下标:%d 值:%d\n", i, s[i])
}
2. range 遍历(常用)
s := []int{10,20,30}
// 下标+值
for idx, val := range s {
fmt.Println(idx, val)
}
// 只取值,忽略下标
for _, val := range s {
fmt.Println(val)
}
// 只取下标
for idx := range s {
fmt.Println(idx)
}
五、切片底层共享问题
切片截取共用底层数组,一个修改全部受影响:
func main() {
arr := [4]int{1,2,3,4}
s1 := arr[:]
s2 := s1[:2]
s2[0] = 99
fmt.Println(arr) // [99 2 3 4]
fmt.Println(s1) // [99 2 3 4]
}
如需独立数据,使用copy生成新切片隔离底层数组。
六、切片作为函数参数
切片是引用类型,函数内修改切片元素会影响外部原切片;
但append扩容后会生成新底层数组,外部切片不受影响:
func modify(s []int) {
s[0] = 99 // 修改原切片
s = append(s, 100) // 扩容,内部生成新切片,外部无变化
}
func main() {
s := []int{1,2,3}
modify(s)
fmt.Println(s) // [99 2 3]
}
七、删除切片元素
Go无直接删除API,通过切片截取实现:
func main() {
s := []int{10,20,30,40}
// 删除下标1元素
s = append(s[:1], s[2:]...)
fmt.Println(s) // [10 30 40]
// 删除头部
s = s[1:]
// 删除尾部
s = s[:len(s)-1]
}
八、二维切片
切片的元素类型为切片,长度各行可不一致(区别于二维数组)
func main() {
// 二维切片
s := [][]int{
{1,2},
{3,4,5},
{6},
}
// 追加一行
s = append(s, []int{7,8,9})
fmt.Println(s[1][2]) // 5
}
九、nil切片使用注意事项
- nil切片可直接使用append、len、cap、range,不会报错
var s []int
s = append(s, 1,2,3)
fmt.Println(s)
- nil切片不能直接下标赋值,底层无数组:
var s []int
s[0] = 1 // panic
十、切片核心总结
- 切片是引用类型,底层基于数组,支持动态扩容
- len为有效元素数,cap为底层数组最大容量
- make创建切片可预分配容量,减少扩容性能损耗
- 切片截取共享底层数组,修改互相影响,copy可隔离
- append追加元素,
...可打散切片合并 - 函数传切片可修改原元素;扩容后内部切片与外部断开关联
- nil切片支持追加、遍历,不能直接下标赋值