今天,我们来聊一聊 Golang 中的 Slice(切片)

4,073 阅读3分钟

前言:
因为工作需要,最近接触 Golang 比较多。
也希望自己的技术栈能从一个 iOS 转变成 iOS / Golang。


那么,今天我们来聊一聊Golang中的数组(Array)和切片(Slice)。

一、Array(数组)

数组我们肯定都是熟悉的,
在Go语言中数组(Array)在初始化后,长度是固定的。

与其他语言类似,在这里举一些在Go中数组的简单demo。

var arr [10]int // 声明,len与cap为10,值默认补0。
arr := []int {1,2,3,4} // 快速声明,len与cap为4,值为[1,2,3,4]。
arr := [5]int {1,2,3,4} // 快速声明,len与cap为5,值为[1,2,3,4,0],末尾默认补0。

// 数组的遍历
for i := 0; i < len(arr); i++ {
	fmt.Println(arr[i])
}

二、Slice(切片)

接下来是今天的主角:Slice。

首先,切片是一种引用类型。(与字典map、通道channel一样,都是引用类型)
有点类似于我们iOS中的MutableArray
暂时可理解为一种可变长的“动态数组”。

注意:
因此,我们取数组的切片时,如果不希望切片的修改会对原有数组产生影响的话,需要对Slice进行Copy操作。

基本使用

numbers := []int{1,2,3,4,5,6,7,8,9,10}

// 半闭半开结构
slice1 := numbers[:] // 直接对numbers的引用
slice1 = numbers[1:10] // 从numbers[1]到numbers[9]
slice1 = numbers[:5] // 从开始到numbers[4]
slice1 = numbers[7:] // 从numbers[7]开始到末尾

slice1 = append(slice1, 1,2,3,4,5) // 追加长度
fmt.Println(slice1) // 打印:[8 9 10 1 2 3 4 5]

slice2 := make([]int, len(slice1), cap(slice1)*2) // 手动声明一个slice2,最大长度cap为slice1的两倍。
copy(slice2,slice1) //将slice1的数据copy到slice2
slice2 = append(slice2,1,2,3) // 在对slice2进行追加

fmt.Println(slice1)  // 打印:[8 9 10 1 2 3 4 5]
fmt.Println(slice2)  // 打印:[8 9 10 1 2 3 4 5 1 2 3]

fmt.Printf("len = %d, cap = %d", len(slice1), cap(slice1)) // 打印当前长度len,与最大容量cap

扩容策略

Slice的长度(len)即将超过最大容量(cap)时,Go语言会对Slice进行扩容。

具体策略为:

  • Slice的长度(len)小于1024时,Go语言会对该Slice进行 “2倍”扩容
  • Slice的长度(len)大于等于1024时,Go语言会对该Slice进行 “1.25倍”扩容
len 扩容策略
<1024 2倍扩容
>=1024 1.25倍扩容

注意:感谢Rolle的补充:当单次扩容也不能满足条件时,会扩容为目标长度的n+1的大小。

感兴趣的同学,可以使用下面的代码,自己试一下它的扩容策略。

slice2 := make([]int,10,10)
fmt.Printf("len = %d, cap = %d\n", len(slice2), cap(slice2)) // 打印当前长度len,与最大容量cap。 // 获取len与cap的长度。
slice2 = append(slice2, 1,2,3,4,5) // 追加数据
fmt.Printf("len = %d, cap = %d\n", len(slice2), cap(slice2)) // 打印当前长度len,与最大容量cap。 // 获取len与cap的长度。

一些注意点(坑)

  1. 切记切记,切片是一种引用类型。如果改切片的值不希望改动原有数据,一定要先copy一份。

  2. 对切片的遍历时,系统会对value进行一次值拷贝。因此不能对value进行引用操作,否则可能会造成panic。如果需要引用操作,请去下标。