数组类型

go语言实战

类型 [n]T 是一个数组,有 n 个类型为 T 的值。

数组定义访问如下,需要指定类型和数组大小:

var a [10]int

注意:数组的长度是其类型的一部分,因此数组不能改变大小

也可以在定义时直接创建数组,数组大小的设置可以有多种方式:

a := [2]string{"a", "b"}
a := []string{"a", "b"}
a := [...]string{"a", "b"}

通过下标访问单个元素:

var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)

golang语言规范

https://golang.org/ref/spec#Array_types

数组是一个单一类型的元素的计数序列,称为元素类型。元素的数量称为数组的长度,绝不是负数。

ArrayType   = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .

长度是数组类型的一部分;它必须计算为一个非负常数,用int类型的值表示。数组a的长度可以通过内置函数len来计算。元素可以用0到len(a)-1的整数索引来寻址。数组类型总是一维的,但可以组成多维类型。

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // same as [2]([2]([2]float64))

Effective Go

https://golang.org/doc/effective_go.html#arrays

在规划内存的详细布局时,数组很有用,有时可以帮助避免分配,但主要是它们是切片的构建模块,也就是下一节的主题。为了给这个主题打下基础,下面说说关于数组的一些情况。

在Go和C中,数组的工作方式有很大的不同,在Go中:

  • 数组就是值。将一个数组赋值给另一个数组会复制所有的元素。
  • 特别是,如果你把一个数组传递给一个函数,它将收到一个数组的副本,而不是一个指向它的指针。
  • 数组的大小是其类型的一部分。类型 [10]int 和 [20]int 是不同的。

值属性可能很有用,但也很昂贵;如果你想要类似C的行为和效率,你可以传递一个指针给数组。

func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // Note the explicit address-of operator

但即使这种风格也不是 go 的习惯用法。请用分片(slice)代替。

实现原理

参考文章: go语言数组的实现原理

初始化

Go 语言中的数组有两种不同的创建方式,一种是显式的指定数组的大小,另一种是使用 [...]T 声明数组,Go 语言会在编译期间通过源代码对数组的大小进行推断:

arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}

编译器会对第二种数组的大小进行推导,通过遍历元素的方式来计算数组中元素的数量。

这两种初始化方式在运行时是完全等价的,[...]T 这种初始化方式只是 Go 语言提供的一种语法糖,当不想计算数组中的元素个数时可以减少一些工作。

对于一个由字面量组成的数组,根据数组元素数量的不同,编译器会做两种不同的优化:

  1. 当元素数量小于或者等于 4 个时,会直接将数组中的元素放置在栈上;
  2. 当元素数量大于 4 个时,会将数组中的元素放置到静态区并在运行时取出;

访问和赋值

无论是在栈上还是静态存储区,数组在内存中其实就是一连串的内存空间。

表示数组的方法就是:

  1. 一个指向数组开头的指针
  2. 数组中元素的数量
  3. 数组中元素类型占的空间大小

数组访问越界是非常严重的错误,Go 语言中对越界的判断:

  1. 可以在编译期间由静态类型检查完成的,函数会对访问数组的索引进行验证。数组和字符串的一些简单越界错误都会在编译期间发现,比如我们直接使用整数或者常量访问数组。
  2. 使用变量去访问数组或者字符串时,需要Go 语言运行时在发现数组、切片和字符串的越界操作触发程序的运行时错误

访问数组:

  • 在使用字面量整数访问数组下标时就会生成非常简单的中间代码
  • 当编译器无法对数组下标是否越界无法做出判断时才会加入 PanicBounds 指令交给运行时进行判断

参考资料