携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
引言
数组是go语言里面中3种可以用于管理集合的数据结构之一,也是另外两种数据结构(切片、映射)的基础数据结构,了解其工作原理有助于理解两外两种数据结构的优雅和功能。
内部实现
一根具有刻度的尺子,就是一个数组的具体实现。
在go语言里面,数组是具有固定长度的数据类型,且只能存储同一类型的数据,在内存上是属于同一内存块。
类比一根长20厘米的刻度尺,每一刻度存储着相同的意义:长X厘米;而刻度则代表元素的索引。
由于数组在内存中是连续分配的,故可以在CPU中缓存更久的时间,而且因为下标的特有属性,使得可以快速迭代数组里面的所有元素。
基础功能
声明和初始化
声明数组时需要指定内部存储的数据类型以及需要存储的元素数量,这个数量称之为数组的长度。
var array [5]int
一旦声明,其存储的类型和长度都无法改变,如果想要存储更多的元素,仅能创建一个更长的数组并将原数组的内容复制到新数组中。在声明的同时将会对数组进行数据的初始化,上述例子的数组存储的值均为整型的0,同理,若是声明字符串数组的话,将会对所有元素进行初始化为“”(空字符串)
若是通过已知数据传入数组中,编译器会通过类型推断获得数组长度。
array := [...]int{1, 2, 3, 4}
fmt.Println("len=", len(array)) // output: len=4
array := [5]int{1: 10, 2: 20}
fmt.Println(array) // output: [0 10 20 0 0]
使用
数组的使用依靠数组的下标进行索引,下表索引如同刻度尺一样,从0开始,作为第一个元素的存储位置。
array := [5]int{1: 10, 2: 20}
fmt.Println(array) // output: [0 10 20 0 0]
array[0] = 100
fmt.Println(array) // output: [100 10 20 0 0]
array[5] = 100 // 无效的 数组 索引 '5' (5 元素的数组超出界限)
复制
在go语言里面,对于程序实体的复制均是深拷贝,也就是拷贝结果和拷贝对象是完全独立的。前提条件是两个数组长度和类型一致,不然将会引发错误。
array := [5]int{1: 10, 2: 20}
array2 := array
array2[0] = 100
fmt.Println(array, array2) // output: [0 10 20 0 0] [100 10 20 0 0]
什么是浅拷贝呢,在python里面对于可变对象(列表、字典等)进行赋值都是浅拷贝。
a = [0, 10, 20, 0, 0]
b = a
b[0] = 100
print(a, b) # [100 10 20 0 0] [100 10 20 0 0]
多维数组
数组本身就是一个维度的,就单是一个x轴的形式,但是可以通过组合多个数组,达成多维数组,不仅仅只有x轴,还有y、z等轴的存在。
// 声明一个二维整型数组,两个维度分别存储 4 个元素和 2 个元素
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化外层数组中索引为 1 个和 3 的元素
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
array[0][0] = 10 // 设置二维数组array的坐标为(0,0)的值为10
函数传递
在函数见传递数组上,由于函数之间传递变量采用值传递(进入函数的是参数的拷贝结果)的,使得内存和性能上开销很大。
故,若需有好的处理结果的话,因考虑使用传入指针,使得函数直接修改数组底层。
// 声明一个需要 8 MB 的数组
var array [1e6]int
// 将数组传递给函数 foo
foo(array)
// 函数 foo 接受一个 100 万个整型值的数组
func foo(array [1e6]int) {
...
}
// 分配一个需要 8 MB 的数组
var array [1e6]int
// 将数组的地址传递给函数 foo
foo(&array)
// 函数 foo 接受一个指向 100 万个整型值的数组的指针
func foo(array *[1e6]int) {
..
}