数组和切片
Go语言中,数组和切片是常用的数据结构,用于存储一系列相同类型的元素。数组是一种固定长度的数据结构,而切片则是对数组的一个可变长度的引用。它们在Go语言中都有着重要的作用,并且在不同的场景中有不同的用途和特点。
数组
在 Go 语言中,数组是固定的一组相同类型的数据,可以使用从 0 开始的下标访问其对应的元素。这一篇文档我们聊介绍这一个数据结构——数组。
数组的声明和初始化
声明一个数组的时候,必须要给与确定的长度:
我们也可以使用初始化个别的值:
需要注意的是,在 Go 语言中,数组是值类型的,而不是引用类型的。这一点和 C 语言不一样,在 C 语言中,数组和字符串都是引用类型的。在 Go 语言中,字符串底层也是字节数组,但是不可变的。
数组的长度可以使用 len 函数:
在控制台打印数组如下:
数组的遍历
如何对数组进行遍历呢?如下示例:
上面的示例中我们使用了 len 这个 Go 内置的函数获取数组的长度,使用了 for 来遍历它。但是,使用 for 和 range 更加的方便,类似于其他语言中的 foreach ,并不需要知道数组的长度:
大概每一种语言都会有自己非常依赖的一种数据结构,比如说 PHP 依赖数组(Array), Python 依赖列表(List), 而 Go 语言依赖切片(Slice)。所以,掌握切片对于用好 Go 来说是非常重要的。这篇文档就来描述切片的相关内容。
切片
切片是一种动态的数组,其底层结构也是使用了数组。
Slice 的多种定义方式
切片的定义方式和数组也非常相似,如下示例:
除此之外,我们还可以使用 make 来初始化一个 Slice:
make 需要传递两个参数,第一个是 slice 的类型,第二个是 slice 的长度。
然后我们再来介绍第三种方法,使用数组来初始化一个 Slice:
需要注意的是, arr[0:4] 从 arr 中获取一个区间,这个区间是 **左闭右开,**即从第 0 个到第 3 个,并不包括第 4 个。如果要获取整个数组,可以使用 arr[:] 。
**Slice 是引用传递,而 Array 是值传递。所以,从 Array 初始化 Slice,如果改变了 Slice 也会相应的改变原来的 Array。**如下示例:
然后我们再来,定义第四种方式,使用 new 关键字:
Slice 的基本操作
因为 slice 是一个动态的数组,我们就可以对它动态的添加、修改、删除值。下面的例子,我们演示了如何追加一个值:
我们也可以通过 copy 函数来追加其他切片中的值:
从切片中删除元素:
基于数组的结构体
Slice 的本质就是基于数组的结构体,这个结构体定义在 runtime/slice.go 文件中,如下:
通过这个 slice 的结构体,我们可以知道切片实际上是对数组的引用。那么 len 和 cap 这两个数值指的是什么呢?我们可以通过下图来了解:

根据上图,我们可以看到len 表示的是数组元素已经使用的长度,而 cap 表示的是数组总的长度,而 cap 减去 len 得到的是数组剩余的容量。
接着我们可以通过输出汇编指令来看 slice 的创建, 创建 Slice 的代码如下:
编译并输出汇编指令:
我们看到第六行生成的汇编指令,解释如下:
第 3 行:创建了一个大小为 3 的数组
第 6 行:
runtime.newobject(SB)创建了一个新结构体的值第 7 行到第 9 行:将三个变量存入 Slice 的结构体
上面对应的代码在 runtime/slice.go 中的 makeslice 方法。
切片的扩容
当我们使用 append 函数对切片追加元素,如果原有的数组结构长度不足以容纳,将会隐式地触发扩容机制,使用扩容算法产生新的数组。其扩容算法如下:
如果新申请容量(Cap)大于2倍地旧容量(Old Cap),那么新生成地数组地容量就为就是申请容量(Cap)。如果不是,接着判断旧 Slice 地长度小于 1024,则最终地容量就是旧容量(Old Cap) 的两倍,即(new cap=doublecap)。否则,就接着判断,旧切片的长度大于等于 1024,则最终容量(New Cap)从旧容量(Old Cap)开始循环增加原来的 1/4,即(New Cap = Old Cap, for {New Cap += NewCap / 4})直到最终容量大于新申请的容量(Cap)。如果最终容量(cap)计算值溢出,则最终容量就会是申请容量 。
需要注意两点:
如果触发了扩容机制,指针就会指向新创建的数组的地址 。
切片的扩容操作,是并发不安全的,需要加锁处理。