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

Go创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

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由运行时下标检查

4. 参考