Go - Array + Slice
数组
逻辑定义
Go 数组:一个
长度固定
,由同构类型元素
组成的连续序列
1 | // 声明了一个数组变量 arr,其类型为 [5]int,其中元素的类型为 int,数组的长度为 5 |
- 数组元素的类型可以为
任意
Go 原生类型或者自定义类型 - 数组的
长度
必须在声明
数组变量时提供- Go 编译器需要在
编译阶段
就知道数组类型的长度(整型数字字面值 or 常量表达式)
- Go 编译器需要在
如果两个数组类型的
元素类型 T
和数组长度 N
都是一样的,那么两个数组类型
是等价
的
1 | func f(arr [5]int) { |
内存表示
Go
编译器
会为 Go 数组分配一整块可容纳所有元素的连续
内存
数组长度
通过
len
函数获取一个数组变量的长度
(元素数量),通过unsafe.SizeOf
函数获取一个数组变量的总大小
1 | arr := [5]int{1, 2, 3, 4, 5} |
初始化
声明一个数组变量时,如果
不进行显式初始化
,那么数组中的元素值就是对应的类型零值
1 | var arr [5]int |
忽略数组长度,用
...
代替,Go编译器
会根据数组初始化元素的个数,自动计算
出数组长度
1 | var arr1 [5]int = [5]int{1, 2, 3, 4, 5} |
对
稀疏数组
进行显式初始化
,可以通过使用下标赋值
的方式来简化代码
1 | var arr = [...]int{ |
数组访问
通过
下标
(从0
开始)访问元素,非常高效
,不存在Go Runtime
带来的额外开销
1 | var arr = [6]int{1, 2, 3, 4, 5, 6} |
多维数组
递归
1 | var mArr [2][3][4]int |
数组类型变量是一个
整体
,即一个数组变量表示的是整个数组在 C 语言中,
数组变量
可视为指向数组第 1 个元素的指针
无论是参与
迭代
,或者作为实际参数
传给一个函数
或者方法
- Go
传递数组
的方式都是纯粹的值拷贝
,这会带来较大的内存拷贝开销
- Go
避免
数组值拷贝
带来的性能损耗
类 C:可以通过使用
指针
的方式,来向函数
传递数组切片
切片
Go 中
最常用
的同构
复合类型
数组缺陷
固定
的元素个数值传递
的开销
较大
长度
无需在声明时指定长度,切片的长度是
动态
的,随着切片中元素的个数的变化而变化
1 | var nums = []int{1, 2, 3, 4, 5, 6} |
实现原理
切片在运行时其实是一个
三元组
结构
1 | type slice struct { |
Field | Desc |
---|---|
array | 指向底层数组 的指针 |
len | 切片的长度 ,即切片当前元素的个数 |
cap | 切片的容量 ,即底层数组的长度 ,cap ≥ len |
Go
编译器
会自动
为每个新建的切片
,建立一个底层数组
,默认底层数组长度
与切片初始元素个数
相同
创建切片
make
通过
make
函数来创建切片,并指定底层数组
的长度
1 | s := make([]byte, 6, 10) |
如果在
make
函数中没有指定cap
参数,那么cap == len
1 | s := make([]byte, 6) |
from array
数组切片化:
array[low:high:max]
,max
的默认值
为数组的长度
,规则为左闭右开
1 | arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} |
切片 sl 的底层数组为 arr,修改切片 sl 中的元素将直接影响数组 arr
1 | sl[0] += 10 |
- 在 Go 中,
数组
更多承担底层存储空间
的角色,切片
为数组的描述符
- 因此,切片在
函数参数传递
时可以避免较大的性能开销
,传递的大小的固定
的(三元组
)
可以在
同一个
底层数组上建立多个切片(共享
同一个底层数组)
1 | arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} |
from slice
原理与 from array 一样,共享
同一个底层数组
1 | arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} |
动态扩容
通过
append
操作向切片追加数据时
如果len == cap
,则Go Runtime
会对这个切片做动态扩容
(重新分配底层数组
)
1 | var sl []int |
扩容系数为
2
,新数组建立后,append 会把旧数组中的数据拷贝
到新数组,旧数组会被GC
掉
自动扩容 -> 解除绑定
1 | u := [...]int{1, 2, 3, 4, 5} |
对比
大多数场合,使用
切片
替代数组
- 切片作为数组的
描述符
,非常轻量
- 无论绑定的底层数组多大,传递切片的
开销
都是恒定可控
的
- 无论绑定的底层数组多大,传递切片的
- 切片 > 数组指针
- 切片支持
下标访问
、边界溢出校验
、动态扩容
等 - 指针本身在 Go 是
受限
的,如不支持指针的算术
运算
- 切片支持