引入

数组长度固定,实际开发中数据量经常变化,Go 提供了切片来解决这个问题。切片是动态长度的序列,底层基于数组实现,比数组灵活得多,是 Go 里最常用的集合类型。

正文

定义

定义

切片是动态长度的序列,比数组更常用。切片本身不存储数据,而是引用一段底层数组。切片有三个属性:指针(指向底层数组的起始位置)、长度len)和容量cap)。

语法

// 字面量创建
sl := []int{1, 2, 3}
 
// make 创建(指定长度和容量)
sl := make([]int, 5, 10)
 
// 从数组或切片切取
sl := arr[low:high]

例子

字面量创建

sl := []int{10, 20, 30}
fmt.Println(sl)       // [10 20 30]
fmt.Println(len(sl))  // 3
fmt.Println(cap(sl))  // 3

字面量创建时长度和容量相等。

make() 创建

sl := make([]int, 3, 5)
fmt.Println(sl)       // [0 0 0]
fmt.Println(len(sl))  // 3
fmt.Println(cap(sl))  // 5

make([]T, len, cap) 创建一个长度 len、容量 cap 的切片,元素为零值。容量省略时等于长度。

从数组切取

arr := [5]int{10, 20, 30, 40, 50}
sl := arr[1:4]
fmt.Println(sl) // [20 30 40]

切片和原数组共享底层数组,修改切片会影响数组。

append() 追加元素

sl := []int{1, 2, 3}
sl = append(sl, 4)
fmt.Println(sl) // [1 2 3 4]
 
sl = append(sl, 5, 6)
fmt.Println(sl) // [1 2 3 4 5 6]

append() 返回新切片,必须用变量接收。容量不够时会自动扩容。

append() 追加另一个切片

a := []int{1, 2}
b := []int{3, 4}
a = append(a, b...)
fmt.Println(a) // [1 2 3 4]

展开切片,把 b 的元素逐个追加到 a

copy() 复制切片

src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
 
dst[0] = 100
fmt.Println(src) // [1 2 3](没变)
fmt.Println(dst) // [100 2 3]

copy() 把数据复制到新切片,两者互不影响

切片的切片

sl := []int{1, 2, 3, 4, 5}
sub := sl[1:3]
fmt.Println(sub)       // [2 3]
fmt.Println(len(sub))  // 2
fmt.Println(cap(sub))  // 4(到底层数组末尾)

子切片的容量从切取位置到底层数组末尾,不是子切片的长度。

扩容规则

sl := make([]int, 0, 4)
sl = append(sl, 1, 2, 3, 4)
fmt.Println(cap(sl)) // 4
 
sl = append(sl, 5)
fmt.Println(cap(sl)) // 8(容量翻倍)

容量不足时,Go 自动分配更大的底层数组并复制数据。容量 < 256 时翻倍,>= 256 时增长 25%。

删除元素

sl := []int{1, 2, 3, 4, 5}
 
// 删除索引 2 的元素
sl = append(sl[:2], sl[3:]...)
fmt.Println(sl) // [1 2 4 5]

Go 没有内置 delete 用于切片,用 append 拼接前后两段来删除。

nil 切片和空切片

var s1 []int        // nil 切片
s2 := []int{}       // 空切片
 
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // false

nil 切片和空切片都可以 append(),行为一致。

常见写法

写法说明
sl := []int{1, 2, 3}字面量创建
sl := make([]int, len, cap)make() 创建
sl := arr[1:4]从数组/切片切取
sl = append(sl, v)追加元素
sl = append(sl, b…)追加另一个切片
copy(dst, src)复制切片
sl[:i]取前 i 个元素
sl[i:]从第 i 个到末尾

特点

  • 切片是引用类型,底层指向数组,赋值和传参共享数据
  • len() 是当前长度,cap() 是底层数组容量
  • append() 容量不够时自动扩容,返回新切片
  • copy() 创建独立副本,修改互不影响
  • 切片切取后和原数组共享底层数组,修改会互相影响
  • 删除元素没有内置函数,用 append(sl[:i], sl[i+1:]…) 实现
  • nil 切片可以直接 append(),不会 panic

理解

切片就是一个”窗口”,透过它看到的是一段底层数组。append() 会让窗口自动变大,copy() 会复制一份独立的数据。日常写 Go 几乎不用数组,全用切片。记住切片和底层数组的关系,就能避免大部分共享数据的坑。

引出

切片是按索引存取数据,如果想用自定义的键来查找数据,接下来看 Go Map,学习用键值对保存数据的集合类型。