### 一、数组 #### 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 ```