在大多数编程语言中, 数组都是最常见的数据储存类型. go也不例外, 而且在go里, slice, map 的底层都是通过数组实现的.


数组的内部实现


在go 里, 数组是一个固定长度, 包含了一段连续的相同类型数据的数据结构. 这些数据类型可以是built-in 比如int, strings, 也可以是其它数据类型如struct.

数组内部示意图 image (图片来自”go in action”)

因为数组在内存里是连续分配的, 因此它使得CPU可以对正在使用的内存数据缓存更久. 而且既然每个数据类型一致, 我们使用迭代方式, 就能以固定速度所以数组中的任意数据. O(1).


数组的声明和初始化


声明一个数组:

  1. 要储存的数据类型.
  2. 要储存的数据类型大小.
// 声明一个包含5个整型数据的数组
var array [5]int
// 类型 int
// 大小 5

要注意, 一旦数组被声明, 那么它的长度跟类型都是不能更改的. 如果你发现数组大小满足不了使用需求, 那么你需要重新建立一个更大的数组, 然后把数据从原来的数组里面拷贝到新的数组里, 再接着使用.

go的一个特性就是, 当一个变量被声明的时候, 它们总是被赋予对应类型的初始值(0值 or zero value). 数组也不例外. 比如上述定义的大小为5的整型数组, 在声明后, 它的初始值其实是{0,0,0,0,0}.

初始化: 初始化没什么好讲的, 很直接:

// [...] 表明 数组大小由初始化的数量决定.
array := [...]int{10,20,30,40,50}


初始化小技巧: 利用0值特性, 有些情况下, 我们只对数组里的部分元素进行赋值. 在数组初始化时, 我们可以通过使用通过元素的位置, 来完成上诉操作.

array :=[5]int{1:10, 2:20}

这样初始化以后, 我们得到的array值是{0,10,20,0,0}.


数组的使用


访问数组中的单个元素:

array := [5]int{10,20,30,40,50}

//把第三个元素值改为35
array[2] = 35

抛开储存值外, 数组也可以用来储存指针.

array := [5]*int{0: new(int), 1: new(int)}
// 给元素1和元素2赋值
*array[0] = 10
*array[1] = 20

附: 上面定义的array的内部示意图 image

在go里面, 数组是个value(值类型). 也就是说数组可以用来彼此之间赋值. 因为可以用变量名代表整个数组, 因此同样类型的数组可以通过变量名赋值. 比如array1 = array2 如果类型不一致, 编译器则会在编译的时候报错.

当然, 复制指针型数组时, 被赋值的是指针值(地址), 而不是指针指向的实际类型值.

//声明第一个字符串型指针数组
var array1 [3]*string

//声明第二个字符串指针型数组并给每个数组元素初始化0值
array2 := [3]*string {new(string), new(string), new(string)}

*array1[0] = "Red"
*array1[1] = "Blue"
*array1[2] = "Green"

// 把数组2的值赋值给数组1
array1 = array2

赋值完之后的数组1和数组2示意图 image

要注意的是, 因为是上面定义的两个数组包含的是指针类型, 所以这时候如果我们修改任一数组元素指针指向值, 那么对应的的另一个数组指针指向值也会发生变化.

//修改数组1第一个元素指向值
*array1[1] = "Black"
fmt.Println(*array2[1])
//输出: black


多维数组


在做数据分析, AI 或者machine learning时, 通常需要用到多维数组. 在go里面, 数组本身是一维的, 但可以通过组合, 很方便的得到多维数组.

//定义一个2x2整型数组
var array [2][2]int

// 访问第二排第二列元素,并复制其为10
array[1][1] = 10

跟数组一样, 只要类型一致, 也可以彼此之间直接赋值.

var array2 [2][2]int
// 为每个元素赋值
array2[0][0] = 10
array2[0][1] = 20
array2[1][0] = 30
array2[1][1] = 40
// 将 array2 的值复制给 array1
array1 = array2


数组在函数之间的传递


根据内存和性能来讲, 在函数之间如果通过值传递(pass by value)来传递数组的话, 是一项开销很大的操作. 举个🌰: 声明一个大小为1M的int数组,在64位架构,意味着800万字节, 也就是8MB内存. 反之, 使用数组指针, 则只需要8个字节即可.

//定义一个100万大小的整型数组
var array [1e6] int

// 传给函数foo
foo(&array)

// 函数foo 接受一个指向100万个整型值数组指针
func foo(array *[1e6]int){
    ...
}