NOTE
1.11 array
1. 是什么 定长的数组 2. 使用 2.1. 数组的长度也是类型 2.2. 数组是值传递 赋值和函数传参时候是复制一份新的数组 2.3. 可以用range迭代 3. 原理 3.1. 数据结构 3.2. 创建 3.2.1. 初始化 - 第一种方式 会调用到cmd/compile/internal/t
这是历史学习笔记,可能存在过时或不完整的理解。
1. 是什么
定长的数组
2. 使用
2.1. 数组的长度也是类型
func TestArray(t *testing.T) {
arrays := [3]int{1, 2, 3}
arrays2 := [4]int{1, 2, 3}
fmt.Println(reflect.DeepEqual(arrays,arrays2))
//fmt.Println(arrays == arrays2)//Invalid operation: arrays == arrays2 (mismatched types [3]int and [4]int)
}
//输出
false
2.2. 数组是值传递
赋值和函数传参时候是复制一份新的数组
func TestArray2(t *testing.T) {
arrays := [...]int{1, 2, 3}
testArray(arrays)
fmt.Println(arrays)
testArrayAddr(&arrays)
fmt.Println(arrays)
}
//这里修改的会反映到调用方
func testArrayAddr(arrays *[3]int) {
arrays[0] = 33333
}
//golang中的array是值传递,会复制一份完全一样的
//注意数组的长度也是属于类型的,即如果参数是[...]int会报错
func testArray(arrays [3]int) {
arrays[0] = 2222
fmt.Println(arrays)
}
//输出
[2222 2 3]
[1 2 3]
[33333 2 3]
2.3. 可以用range迭代
func TestArray3(t *testing.T) {
a := [...]int{999, 888, 777}
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
fmt.Println("==========")
for i, v := range a {
fmt.Printf("a[%d]: %d\n", i, v)
}
fmt.Println("==========")
for i := 0; i < len(a ); i++ {
fmt.Printf("a[%d]: %d\n", i, a [i])
}
}
//输出
a[0]: 999
a[1]: 888
a[2]: 777
==========
a[0]: 999
a[1]: 888
a[2]: 777
==========
a[0]: 999
a[1]: 888
a[2]: 777
3. 原理
3.1. 数据结构
type Array struct {
Elem *Type // 类型
Bound int64 // 长度
}
3.2. 创建
3.2.1. 初始化
- 第一种方式
arr1 := [3]int{1, 2, 3}
会调用到cmd/compile/internal/types.NewArray
func NewArray(elem *Type, bound int64) *Type {
if bound < 0 {
Fatalf("NewArray: invalid bound %v", bound)
}
t := New(TARRAY)
//Array包含两个字段Elem和Bound
t.Extra = &Array{Elem: elem, Bound: bound}
//当前数组是否应该在堆栈中初始化也在编译期就确定了
t.SetNotInHeap(elem.NotInHeap())
return t
}
- 第二种方式
arr2 := [...]int{1, 2, 3}
一样会调用cmd/compile/internal/types.NewArray,Bound为-1 后面调用到cmd/compile/internal/gc.typecheckcomplit设置长度
func typecheckcomplit(n *Node) (res *Node) {
...
switch t.Etype {
case TARRAY, TSLICE:
var length, i int64
nl := n.List.Slice()
for i2, l := range nl {
i++
if i > length {
length = i
}
}
//如果是... 那么重新设置长度
if t.IsDDDArray() {
t.SetNumElem(length)
}
}
}
3.2.2. 字面量存储的位置
func anylit(n *Node, var_ *Node, init *Nodes) {
t := n.Type
switch n.Op {
case OSTRUCTLIT, OARRAYLIT:
if n.List.Len() > 4 {
...
}
fixedlit(inInitFunction, initKindLocalCode, n, var_, init)
...
}
}
当元素数量小于或者等于 4 个时,会直接将数组中的元素放置在栈上
func fixedlit(ctxt initContext, kind initKind, n *Node, var_ *Node, init *Nodes) {
var splitnode func(*Node) (a *Node, value *Node)
...
for _, r := range n.List.Slice() {
a, value := splitnode(r)
a = nod(OAS, a, value)
a = typecheck(a, ctxStmt)
switch kind {
case initKindStatic:
genAsStatic(a)
//走到下面,相当于
//var arr [3]int
//arr[0] = 1
//arr[1] = 2
//arr[2] = 3
case initKindLocalCode:
a = orderStmtInPlace(a, map[string][]*Node{})
a = walkstmt(a)
init.Append(a)
}
}
}
当元素数量大于 4 个时,会将数组中的元素放置到静态区并在运行时取出
func anylit(n *Node, var_ *Node, init *Nodes) {
t := n.Type
switch n.Op {
case OSTRUCTLIT, OARRAYLIT:
if n.List.Len() > 4 {
vstat := staticname(t)
vstat.Name.SetReadonly(true)
//相当于
//var arr [5]int
//statictmp_0[0] = 1
//statictmp_0[1] = 2
//statictmp_0[2] = 3
//statictmp_0[3] = 4
//statictmp_0[4] = 5
//arr = statictmp_0
fixedlit(inNonInitFunction, initKindStatic, n, vstat, init)
a := nod(OAS, var_, vstat)
a = typecheck(a, ctxStmt)
a = walkexpr(a, init)
init.Append(a)
break
}
...
}
}
3.3. 访问和赋值
3.3.1. 下标检查
使用常量数组下标,会到达cmd/compile/internal/gc.typecheck1由编译时进行越界检查
func typecheck1(n *Node, top int) (res *Node) {
switch n.Op {
case OINDEX:
ok |= ctxExpr
l := n.Left // array
r := n.Right // index
switch n.Left.Type.Etype {
case TSTRING, TARRAY, TSLICE:
...
//访问数组的索引是非整数时
if n.Right.Type != nil && !n.Right.Type.IsInteger() {
yyerror("non-integer array index %v", n.Right)
break
}
//访问数组的索引是负数时
if !n.Bounded() && Isconst(n.Right, CTINT) {
x := n.Right.Int64()
if x < 0 {
yyerror("invalid array index %v (index must be non-negative)", n.Right)
}
//访问数组的索引越界时
else if n.Left.Type.IsArray() && x >= n.Left.Type.NumElem() {
yyerror("invalid array index %v (out of bounds for %d-element array)", n.Right, n.Left.Type.NumElem())
}
}
}
...
}
}
如果使用的是变量数组下标,会到达runtime.goPanicIndex由运行时下标检查