go切片(slice)

100 次阅读

一、切片基础概念

1. 切片是什么

切片是基于数组封装的动态、可变长引用类型,解决数组长度固定的缺陷,是Go开发最常用的数据结构。
底层结构包含三部分:

  1. 指针:指向底层数组起始位置
  2. 长度 len:切片当前存储元素个数,len() 获取
  3. 容量 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切片使用注意事项

  1. nil切片可直接使用append、len、cap、range,不会报错
var s []int
s = append(s, 1,2,3)
fmt.Println(s)
  1. nil切片不能直接下标赋值,底层无数组:
var s []int
s[0] = 1 // panic

十、切片核心总结

  1. 切片是引用类型,底层基于数组,支持动态扩容
  2. len为有效元素数,cap为底层数组最大容量
  3. make创建切片可预分配容量,减少扩容性能损耗
  4. 切片截取共享底层数组,修改互相影响,copy可隔离
  5. append追加元素,...可打散切片合并
  6. 函数传切片可修改原元素;扩容后内部切片与外部断开关联
  7. nil切片支持追加、遍历,不能直接下标赋值