Go 语言数组
数组概念
数组是一组具有相同且唯一类型、长度固定的数据集合,类型可以是任意原始类型。例如:整型、字符串或自定义类型。数组长度必须是一个常量表达式,并且必须是一个非负整数。数组长度也是数组类型的一部分,所以 [5]int 和 [10]int 是属于不同类型的。数组编译时,值的初始化是按照数组顺序完成的。
注意:如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型。当使用值时我们必须先做一个类型判断,后续会有讲解。
数组元素可以通过 索引 来获取,索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。元素的数量,也称为数组的长度或者大小。数组长度必须在声明该数组时就给出(编译时需要知道数组长度以便分配内存),数组长度最大为 2Gb。
数组的声明格式:
var identifier [len]type
例如:
var arr1 [5]int
上面的数组每个元素都是一个整型值,当声明数组时所有的元素都会被自动初始化为默认值 0。
arr1 的长度是 5,索引范围从 0 到 len(arr1)-1。总体来说索引 i 代表的元素是 arr1[i],最后一个元素是 arr1[len(arr1)-1]。
对索引项为 i 的数组元素赋值:arr[i] = value,所以数组是 可变的。
当索引大于等于 len(arr1) 时,如果编译器可以检测到,会给出索引超限的提示信息,如果检测不到,编译会通过,但运行时会抛出异常:
runtime error: index out of range
由于索引的存在,数组自然就可以通过 for 去遍历:
package mainimport "fmt"func main() {var arr1 [5]intfor i:=0; i < len(arr1); i++ {arr1[i] = i * 2}for i:=0; i < len(arr1); i++ {fmt.Printf("Array at index %d is %d\n", i, arr1[i])}}
输出结果:
Array at index 0 is 0Array at index 1 is 2Array at index 2 is 4Array at index 3 is 6Array at index 4 is 8
for 循环中,条件 i < len(arr1)非常重要,如果写成 i <= len(arr1) 的话会产生索引越界的错误。
通过 for 遍历数组并赋值:
for i:=0; i < len(arr1); i++{arr1[i] = ...}
也可以使用 for-range 的方式,在这里i也是数组的索引:
for i,_:= range arr1 {...}
Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new() 来创建:
var arr1 = new([5]int)
那么这种方式和 var arr2 [5]int 的区别是什么呢?
arr1 的类型是 *[5]int,而 arr2 的类型是 [5]int。
这样的结果就是当把一个数组赋值给另一个时,需要再做一次数组内存的拷贝操作。例如:
arr2 := *arr1arr2[2] = 100
这样两个数组就有了不同的值,在赋值后修改 arr2 不会对 arr1 生效。
所以在函数中,数组作为参数传入时,如 func1(arr2),会产生一次数组拷贝,func1 方法不会修改原始的数组 arr2。
如果你想修改原数组,那么 arr2 必须通过 & 操作符以 引用方式 传过来,例如func1(&arr2)。
示例:
package mainimport "fmt"func f(a [3]int) { fmt.Println(a) }func fp(a *[3]int) { fmt.Println(a) }func main() {var ar [3]intf(ar) // passes a copy of arfp(&ar) // passes a pointer to ar}
输出结果:
[0 0 0]&[0 0 0]
另一种方法就是生成数组切片并将其传递给函数。
练习1:array_value.go: 证明当数组赋值时,发生了数组内存拷贝。
练习2:for_array.go: 写一个循环并用下标给数组赋值(从 0 到 15)并且将数组打印在屏幕上。
数组常量
如果数组值已经提前知道了,那么可以通过 数组常量 的方法来初始化数组,而不用依次使用 []=方法。
package mainimport "fmt"func main() {// var arrAge = [5]int{18, 20, 15, 22, 16}// var arrLazy = [...]int{5, 6, 7, 8, 22}// var arrLazy = []int{5, 6, 7, 8, 22} //注:初始化得到的实际上是切片slicevar arrKeyValue = [5]string{3: "Chris", 4: "Ron"}// var arrKeyValue = []string{3: "Chris", 4: "Ron"} //注:初始化得到的实际上是切片slicefor i:=0; i < len(arrKeyValue); i++ {fmt.Printf("Person at %d is %s\n", i, arrKeyValue[i])}}
第一种变化:
var arrAge = [5]int{18, 20, 15, 22, 16}
[5]int 可以从左边起开始忽略:[10]int {1, 2, 3},这是一个有 10 个元素的数组,除了前三个元素外其他元素都为 0。
第二种变化,忽略 ... :
var arrLazy = []int{5, 6, 7, 8, 22}
第三种变化:key: value 语法
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
只有索引 3 和 4 被赋予实际的值,其他元素都被设置为空的字符串,所以输出结果为:
Person at 0 isPerson at 1 isPerson at 2 isPerson at 3 is ChrisPerson at 4 is Ron
在这里数组长度同样可以写成 ...。
你可以取任意数组常量的地址来作为指向新实例的指针:
package mainimport "fmt"func fp(a *[3]int) { fmt.Println(a) }func main() {for i := 0; i < 3; i++ {fp(&[3]int{i, i * i, i * i * i})}}
输出结果:
&[0 0 0]&[1 1 1]&[2 4 8]
几何点(或者数学向量)是一个使用数组的经典例子。为了简化代码通常使用一个别名:
type Vector3D [3]float32var vec Vector3D
多维数组
数组通常是一维的,但是可以用来组装成多维数组,例如:[3][5]int,[2][2][2]float64。
内部数组总是长度相同的。Go 语言的多维数组是矩形式的(切片的数组是唯一的例外)。
多维数组示例:
package mainconst (WIDTH = 1920HEIGHT = 1080)type pixel intvar screen [WIDTH][HEIGHT]pixelfunc main() {for y := 0; y < HEIGHT; y++ {for x := 0; x < WIDTH; x++ {screen[x][y] = 0}}}
数组传递给函数
当我们需要把一个大数组传递给函数时,这样会消耗很多内存。有两种方法可以避免这种现象:
- 传递数组的指针
- 使用数组的切片
下面的例子演示了第一种方法:
package mainimport "fmt"func main() {array := [3]float64{7.0, 8.5, 9.1}x := Sum(&array) // Note the explicit address-of operator// to pass a pointer to the arrayfmt.Printf("The sum of the array is: %f", x)}func Sum(a *[3]float64) (sum float64) {for _, v := range a { // derefencing *a to get back to the array is not necessary!sum += v}return}
输出结果:
The sum of the array is: 24.600000
但这在 Go 中并不常用,通常使用切片,在下一节会说到。。