### 一、数组

#### 1. 定义

- 一个长度固定的、由同构类型（相同类型）元素组成的连续序列
- `var arr [N]T`
- 数组变量 arr，类型为`[N]T`，长度为N，元素类型为T
- 如果两个数组类型的元素类型 T 与数组长度 N 都是一样的，那么这两个数组类型是等价的
- 如果有一个属性不同，它们就是两个不同的数组类型
- 通过`len`方法获得数组长度
- 通过`unsafe.Pointer()`可以获得数组总的占用空间
- 数组做为函数参数，值拷贝，性能代价高

#### 2. 初始化

- 定义但不初始化
- 定义且初始化并指定长度
- 定义且初始化当不指定长度
- 下标赋值的方式对它进行初始化
```go

var arr1 [6]int // 没有显示初始化，数据默认为零值，[0 0 0 0 0 0]

var arr2 = [6]int {
    11, 12, 13, 14, 15, 16,
} // [11 12 13 14 15 16]

var arr3 = [...]int { 
    21, 22, 23,
} // 编译器自动推断数组的长度，[21 22 23]
fmt.Printf("%T\n", arr3) // [3]int

// 下标赋值的方式对它进行初始化
var arr4 = [...]int{
    99: 39, // 将第100个元素(下标值为99)的值赋值为39，其余元素值均为0
}
fmt.Printf("%T\n", arr4) // [100]int
```

#### 3. 底层方法

- 初始化

```go
// NewArray returns a new fixed-length array Type.
func NewArray(elem *Type, bound int64) *Type {
	if bound < 0 {
		Fatalf("NewArray: invalid bound %v", bound)
	}
	t := New(TARRAY)
	t.Extra = &Array{Elem: elem, Bound: bound}
	t.SetNotInHeap(elem.NotInHeap())
	return t
}
```

- 长度推导
  - 定义时指定长度，变量类型和长度会在编译中的类型检查阶段取出来，直接调用NewArray方法创建一个Array
  - 未指定长度会在编译中的类型检查阶段调用typecheckcomplit阶段推导出长度

```go
// The result of typecheckcomplit MUST be assigned back to n, e.g.
// 	n.Left = typecheckcomplit(n.Left)
func typecheckcomplit(n *Node) (res *Node) {
	// Need to handle [...]T arrays specially.
	if n.Right.Op == OTARRAY && n.Right.Left != nil && n.Right.Left.Op == ODDD {
		n.Right.Right = typecheck(n.Right.Right, ctxType)
		if n.Right.Right.Type == nil {
			n.Type = nil
			return n
		}
		elemType := n.Right.Right.Type // 元素类型

		length := typecheckarraylit(elemType, -1, n.List.Slice(), "array literal") // 数组长度

		n.Op = OARRAYLIT
		n.Type = types.NewArray(elemType, length)
		n.Right = nil
		return n
	}
  switch t.Etype {
	default:
		yyerror("invalid composite literal type %v", t)
		n.Type = nil

	case TARRAY:
		typecheckarraylit(t.Elem(), t.NumElem(), n.List.Slice(), "array literal") // 对数组进行元素和索引的检查
		n.Op = OARRAYLIT
		n.Right = nil
  // ... 省略代码
}
```

- 数组越界检查
  - 使用常量或整数数值在静态编译期间可以验出数组越界风险

```go
func typecheck1(n *Node, top int) (res *Node) {
	if enableTrace && trace {
		defer tracePrint("typecheck1", n)(&res)
	}

	switch n.Op {
    case OINDEX:
		ok |= ctxExpr
		n.Left = typecheck(n.Left, ctxExpr)
		n.Left = defaultlit(n.Left, nil)
		n.Left = implicitstar(n.Left)
		l := n.Left // 数组
		n.Right = typecheck(n.Right, ctxExpr) // 索引
		r := n.Right
    t := l.Type
    case TSTRING, TARRAY, TSLICE: // 判断是数组
			n.Right = indexlit(n.Right)
			if n.Right.Type != nil && !n.Right.Type.IsInteger() {
				yyerror("non-integer %s index %v", why, n.Right)
				break
			}

			if !n.Bounded() && Isconst(n.Right, CTINT) {
				x := n.Right.Int64()
				if x < 0 {
					yyerror("invalid %s index %v (index must be non-negative)", why, n.Right)
				} else if t.IsArray() && x >= t.NumElem() { // 数组越界
					yyerror("invalid array index %v (out of bounds for %d-element array)", n.Right, t.NumElem())
				}
			}
```

- 运行时数组越界
  - runtime.panicindex

```go
TEXT runtime·panicIndex(SB),NOSPLIT,$0-16
	MOVQ	AX, x+0(FP)
	MOVQ	CX, y+8(FP)
	JMP	runtime·goPanicIndex(SB)
```



- runtime.goPanicIndex

```go
func goPanicIndex(x int, y int) {
	panicCheck1(getcallerpc(), "index out of range")
	panic(boundsError{x: int64(x), signed: true, y: y, code: boundsIndex})
}
```



### 切片

- 定义切片变量时，不像数组一样定义长度
- 长度容量不固定
- len 返回切片长度
- append 向切片中追加元素
- 切片传参数，相当于传递底层数组的描述符

#### 切片结构
- 底层运行时结构
- array 底层数组的指针
- len 切片长度
- cap 切片容量
```go

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
```

#### 切片定义与初始化
- 通过make创建切片，可以指定长度与容量
    - `var arr = make([]int, 3, 6)` 类型，长度，容量
    - 不指定容量，cap=len
- 采用 array[low : high : max]语法基于一个已存在的数组创建切片。这种方式被称为数组的切片化
    - 从原数组起始位置low开始
    - 新数组长度 high - low
    - 数组容量是 max - low
    - 对新切片中元素的修改将直接影响原数组
    - 新数组的容量和长度不能大于原数组
```go
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sl := arr[3:7:9]
```
![](https://blog-heysq-1255479807.cos.ap-beijing.myqcloud.com/blog/wiki/go/arr_low_high_max.jpg)   

- 基于切片创建切片

#### 切片动态扩容
- 翻倍扩容
- 基于一个已有数组建立的切片，一旦追加的数据操作触碰到切片的容量上限（实质上也是数组容量的上界)，切片就会和原数组解除“绑定”，后续对切片的任何修改都不会反映到原数组中

### 数组与切片互相转换（go1.17后官方支持）
- 数组转切片
```go
func main() {
	arr := [3]int{1, 2, 3}
	sli := arr[:]
	fmt.Println(sli)
}
```

- 切片转数组 `unsafe` 方法
```go
func main() {
	b := []int{1, 2, 3}
	p := (*[3]int)(unsafe.Pointer(&b[0]))
	(*p)[1]+= 10
	fmt.Println(*p)
}
```

- 切片转数组官方方法，转换后的数组长度不能大于原切片的长度，nil 切片或 cap 为 0 的 empty 切片都可以被转换为一个长度为 0 的数组指针
```go
func main() {
	b := []int{1, 2, 3}
	p := (*[3]int)(b)
	(*p)[1] += 10
	fmt.Println(*p)
}
```

### 函数传参性能
- 传参都是值拷贝
- slice拷贝的是引用结构
- array拷贝的是数组的值（包含元素）
```go
a1 := [16]int{}
	d1 := func(arr [16]int) int {
		return len(arr)
	}

	f1 := func() int {
		return d1(a1)
	}

	a2 := [65535]int{}
	d2 := func(arr [65535]int) int {
		return len(arr)
	}
	f2 := func() int {
		return d2(a2)
	}

	for _, f := range []func() int{f1, f2} {
		start := time.Now()
		for i := 0; i < 10000; i++ {
			f()
		}
		fmt.Printf("time.Since(start).Milliseconds(): %v\n", time.Since(start).Microseconds())
	}
    // 88
    // 398362
```