6.6 KiB
6.6 KiB
一、数组
1. 定义
- 一个长度固定的、由同构类型(相同类型)元素组成的连续序列
var arr [N]T
- 数组变量 arr,类型为
[N]T
,长度为N,元素类型为T - 如果两个数组类型的元素类型 T 与数组长度 N 都是一样的,那么这两个数组类型是等价的
- 如果有一个属性不同,它们就是两个不同的数组类型
- 通过
len
方法获得数组长度 - 通过
unsafe.Pointer()
可以获得数组总的占用空间 - 数组做为函数参数,值拷贝,性能代价高
2. 初始化
- 定义但不初始化
- 定义且初始化并指定长度
- 定义且初始化当不指定长度
- 下标赋值的方式对它进行初始化
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. 底层方法
- 初始化
// 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阶段推导出长度
// 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
// ... 省略代码
}
- 数组越界检查
- 使用常量或整数数值在静态编译期间可以验出数组越界风险
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
TEXT runtime·panicIndex(SB),NOSPLIT,$0-16
MOVQ AX, x+0(FP)
MOVQ CX, y+8(FP)
JMP runtime·goPanicIndex(SB)
- runtime.goPanicIndex
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 切片容量
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
- 对新切片中元素的修改将直接影响原数组
- 新数组的容量和长度不能大于原数组
arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
sl := arr[3:7:9]
- 基于切片创建切片
切片动态扩容
- 翻倍扩容
- 基于一个已有数组建立的切片,一旦追加的数据操作触碰到切片的容量上限(实质上也是数组容量的上界),切片就会和原数组解除“绑定”,后续对切片的任何修改都不会反映到原数组中
数组与切片互相转换(go1.17后官方支持)
- 数组转切片
func main() {
arr := [3]int{1, 2, 3}
sli := arr[:]
fmt.Println(sli)
}
- 切片转数组
unsafe
方法
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 的数组指针
func main() {
b := []int{1, 2, 3}
p := (*[3]int)(b)
(*p)[1] += 10
fmt.Println(*p)
}
函数传参性能
- 传参都是值拷贝
- slice拷贝的是引用结构
- array拷贝的是数组的值(包含元素)
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