数组类型
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 语言提供的一种语法糖,当不想计算数组中的元素个数时可以减少一些工作。
对于一个由字面量组成的数组,根据数组元素数量的不同,编译器会做两种不同的优化:
- 当元素数量小于或者等于 4 个时,会直接将数组中的元素放置在栈上;
- 当元素数量大于 4 个时,会将数组中的元素放置到静态区并在运行时取出;
访问和赋值
无论是在栈上还是静态存储区,数组在内存中其实就是一连串的内存空间。
表示数组的方法就是:
- 一个指向数组开头的指针
- 数组中元素的数量
- 数组中元素类型占的空间大小
数组访问越界是非常严重的错误,Go 语言中对越界的判断:
- 可以在编译期间由静态类型检查完成的,函数会对访问数组的索引进行验证。数组和字符串的一些简单越界错误都会在编译期间发现,比如我们直接使用整数或者常量访问数组。
- 使用变量去访问数组或者字符串时,需要Go 语言运行时在发现数组、切片和字符串的越界操作触发程序的运行时错误
访问数组:
- 在使用字面量整数访问数组下标时就会生成非常简单的中间代码
- 当编译器无法对数组下标是否越界无法做出判断时才会加入
PanicBounds
指令交给运行时进行判断