本文重构于 Go 切片(Slice)

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,容量(cap)不够时会自动扩容

切片为引用类型,从数组中得到的切片修改元素值时,原数组也会发生变化,修改原数组时,切片也会变化

创建切片

创建切片可以有四种方法

使用 var

1
var s []uint32

指定字面值

这样创建的切片长度和容量都等于初始元素个数

1
s := []uint32{1, 2, 3}

使用 make() 函数

可以指定长度和容量,格式为make([]type, len[, cap]),可以只指定长度,也可以长度容量同时指定

1
2
3
s1 := make([]uint32)
s2 := make([]uint32, 1)
s3 := make([]uint32, 1, 10)

使用截取符

可以从现成的数组或切片创建

1
2
3
4
5
var arr [10]uint32
s1 := arr[0:5]
s2 := arr[:5]
s3 := arr[5:]
s4 := arr[:]

访问切片

  1. 直接使用下标
  2. 使用截取符
  3. 使用 for k,v:= range s

添加元素

使用append()函数向切片中添加元素,可以一次添加 0 个或多个元素,如果容量不够会自动扩容

1
2
3
4
5
6
7
s := make([]uint32, 0, 4)

s = append(s, 1, 2, 3)
fmt.Println(len(s), cap(s)) // 3 4

s = append(s, 4, 5, 6)
fmt.Println(len(s), cap(s)) // 6 8

若要添加其他切片/数组或部分内容,可以使用下面方法

1
s = append(s, s1...)

修改元素

可以通过下标直接修改

注意,在你使用 for k,v:= range s 时,不能直接通过修改 v 来修改原切片,必须使用 s[k] 来修改(就像 Python 里面一样,你 range 出来的都是副本)

删除元素

截取需要的那部分再赋值回去就是删除

如果要删除中间的某个元素,可以使用下面的方法

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

拷贝

如果直接赋值,指向的底层数组是一样的,修改一个另一个也会跟着变

如果不想这样,可以使用 copy() 函数

此函数将内容从一个数组切片复制到另一个数组切片。如果加入的两个数组切片不一样大,就会按其中较小的那个数组切片的元素个数进行复制

1
2
3
4
5
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}

copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置

开发相关问题

如何比较两个切片

与数组不同,切片之间不能直接使用 == 来比较(唯一的例外是和 nil 比较)

如果是 []byte ,可以使用标准库提供的高度优化的 bytes.Equal 函数来判断是否相等

对于其他类型,只能自己一个个枚举

1
2
3
4
5
6
7
8
9
10
11
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
for i := range x {
if x[i] != y[i] {
return false
}
}
return true
}

判断切片为空

使用 len(s) == 0 来判断,而不应该用 s == nil 来判断

1
2
3
4
var s []int    // len(s) == 0, s == nil
s = nil // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{} // len(s) == 0, s != nil

自动扩容的细节

还是有点复杂的,详情可以看这里