Array 数组
1.初始化
存储结构
编译期间的数组类型:
type Array struct {
Elem *Type // element type
Bound int64 // number of elements; <0 if unknown yet
}
由cmd/compile/internal/types.NewArray
函数生成的,该类型包含两个字段,分别是元素类型 Elem
和数组的大小 Bound
,这两个字段共同构成了数组类型,而当前数组是否应该在堆栈中初始化也在编译期就确定了。
func NewArray(elem *Type, bound int64) *Type {
if bound < 0 {
base.Fatalf("NewArray: invalid bound %v", bound)
}
t := New(TARRAY)
t.Extra = &Array{Elem: elem, Bound: bound}
t.SetNotInHeap(elem.NotInHeap())
if elem.HasTParam() {
t.SetHasTParam(true)
}
return t
}
array初始化方式
**编译期函数遍历阶段
完成对数组的初始化:**数组是内存中一片连续的区域,需要在初始化时被指定长度,数 组的大小取决于数组中存放的元素大小。对于数组还有一种语法糖, 可以不用指定类型,如arr3:=[…]int{2,3,4},这种声明方式在 编译时自动推断长度。
-
字面量初始化
var arr = [4]int{1,2,3,4} arr1 := [4]int{1,2,3,4}
-
定长空数组
var arr2 = [4]int{}
-
语法糖自动推断长度
arr3 := [...]int{2,3,4}
-
使用new创建数组
arr4 := new([3]int)
anylit函 数用于处理各种类型的字面量。当数组的长度小于4时,在运行时数组 会被放置在栈中,如果当前数组的元素大于四个,cmd\compile\internal\walk\complit.go\anylit
会先获取一个唯一的 staticname
,然后调用cmd\compile\internal\walk\complit.go.fixedlit
函数在静态存储区初始化数组中的元素并将临时变量赋值给数组:
编译时初始化:
-
声明一块连续的内存空间
-
将字面量值依次放入数组中
fixedlit
函数将执行数组初始化与赋值的逻辑。fixedlit
函数伪代码:var arr = [3]int{1, 2, 3} // 变为: var arr [3]int arr[0] = 1 arr[1] = 2 arr[2] = 3
2. 数组的可比性
- Go 语言数组在初始化之后大小就无法改变。
- 存储元素类型相同、但是大小不同的数组类型在 Go 语言看来也是完全不同的,只有两个条件都相同才是同一类型,也就是说数组的大小也作为类型的描述故
[3]int
和[5]int
完全是不同的类型。
测试能否对数组重新赋值
如下代码段,企图通过赋值改变arr2
数组大小
func main() {
arr1 := [5]int{1, 2, 3, 4, 5}// arr1为具有5个int元素的数组
arr2 := [3]int{1, 2, 3} // arr2为具有3个int元素的数组
arr1 = arr2
arr1 = [3]int{}
}
使用Go提供的 test
命令对该段代码测试,可以看出无论是使用arr2
数组或是空数组对arr1
都会在编译期报错:
测试数组的可比性
存在如下4个数组
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 3}
arr4 := [3]int{4, 4, 4}
尝试使用if
对 arr1
与arr2
进行对比:
func main() {
arr1 := [5]int{1, 2, 3, 4, 5}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 3}
arr4 := [3]int{4, 4, 4}
if arr1 == arr2 {
}
}
使用Go提供的 test
命令对该段代码测试:
因为arr1与arr2不是相同的类型所以无法比较
如下代码测试结果为:
true
false
说明,仅当两个数组类型(元素类型、数组大小)且对应下标的元素值相同时,比较结果才为true
func main() {
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 3}
arr4 := [3]int{2, 2, 3}
if arr3 == arr2 {
fmt.Println("true")
} else {
fmt.Println("false")
}
if arr4 == arr2 {
fmt.Println("true")
} else {
fmt.Println("false")
}
}
3.数组值复制
与C语言中的数组显著不同的是,Go语言中的数组在赋值和函数调 用时的形参都是值复制。
如下代码段
func ChangeArray(arr [3]int) {
arr[0] = 99
fmt.Printf("In func arr Addr: %p Value: %v\n", &arr, arr)
}
func main() {
arr2 := [3]int{1, 2, 3}
temp := arr2
fmt.Printf("temp Addr: %p Value: %v\n", &temp, temp)
fmt.Printf("Befor func arr2 Addr: %p Value: %v\n", &arr2, arr2)
ChangeArray(arr2)
fmt.Printf("After func arr2 Addr: %p Value: %v\n", &arr2, arr2)
}
执行结果:赋值操作、函数传递都会触发数组复制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJ4peu05-1648342979285)(https://raw.githubusercontent.com/Voryla/myimages/main/deepinJVM/20220318143612.png)]
4.数组的越界访问问题
数组访问越界是非常严重的错误,Go语言中对越界的判断有一部 分是在编译期间类型检查阶段完成的,typecheck1函数会对访问数组 的索引进行验证。
数组越界的判定分为三种情况:
- 访问数组的索引是非整数时报错为non-integer array index%v。
- 访问数组的索引是负数时报错为invalid array index%v(index must be non-negative)。
- 访问数组的索引越界时报错为invalid array index%v(out of bounds for%d-element array)。
总结
数组在初始化时,在不考虑逃逸分析的情况下,如果数组中元素的个数小于或者等于 4 个,那么所有的变量会直接在栈上初始化,如果数组元素大于 4 个,变量就会在静态存储区初始化然后拷贝到栈上;在初始化之后,无法更改数组的大小。仅当两个数组类型(元素类型、数组大小)且对应下标的元素值相同时,比较结果才为true;赋值操作和函数传递都会触发数组复制。