數(shù)組是具有相同 唯一類型 的一組已編號且長度固定的數(shù)據(jù)項(xiàng)序列(這是一種同構(gòu)的數(shù)據(jù)結(jié)構(gòu));這種類型可以是任意的原始類型例如整型、字符串或者自定義類型。數(shù)組長度必須是一個(gè)常量表達(dá)式,并且必須是一個(gè)非負(fù)整數(shù)。數(shù)組長度也是數(shù)組類型的一部分,所以[5]int和[10]int是屬于不同類型的。數(shù)組的編譯時(shí)值初始化是按照數(shù)組順序完成的(如下)。
注意事項(xiàng) 如果我們想讓數(shù)組元素類型為任意類型的話可以使用空接口作為類型(參考 第 11 章)。當(dāng)使用值時(shí)我們必須先做一個(gè)類型判斷(參考 第 11 章)。
數(shù)組元素可以通過 索引(位置)來讀取(或者修改),索引從 0 開始,第一個(gè)元素索引為 0,第二個(gè)索引為 1,以此類推。(數(shù)組以 0 開始在所有類 C 語言中是相似的)。元素的數(shù)目,也稱為長度或者數(shù)組大小必須是固定的并且在聲明該數(shù)組時(shí)就給出(編譯時(shí)需要知道數(shù)組長度以便分配內(nèi)存);數(shù)組長度最大為 2Gb。
聲明的格式是:
var identifier [len]type
例如:
var arr1 [5]int
在內(nèi)存中的結(jié)構(gòu)是:http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.1_fig7.1.png?raw=true" alt="" />
每個(gè)元素是一個(gè)整型值,當(dāng)聲明數(shù)組時(shí)所有的元素都會(huì)被自動(dòng)初始化為默認(rèn)值 0。
arr1 的長度是 5,索引范圍從 0 到 len(arr1)-1。
第一個(gè)元素是 arr1[0],第三個(gè)元素是 arr1[2];總體來說索引 i 代表的元素是 arr1[i],最后一個(gè)元素是 arr1[len(arr1)-1]。
對索引項(xiàng)為 i 的數(shù)組元素賦值可以這么操作:arr[i] = value,所以數(shù)組是 可變的。
只有有效的索引可以被使用,當(dāng)使用等于或者大于 len(arr1) 的索引時(shí):如果編譯器可以檢測到,會(huì)給出索引超限的提示信息;如果檢測不到的話編譯會(huì)通過而運(yùn)行時(shí)會(huì) panic:(參考 第 13 章)
runtime error: index out of range
由于索引的存在,遍歷數(shù)組的方法自然就是使用 for 結(jié)構(gòu):
示例 7.1 for_arrays.go
package main
import "fmt"
func main() {
var arr1 [5]int
for 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])
}
}
輸出結(jié)果:
Array at index 0 is 0
Array at index 1 is 2
Array at index 2 is 4
Array at index 3 is 6
Array at index 4 is 8
for 循環(huán)中的條件非常重要:i < len(arr1),如果寫成 i <= len(arr1) 的話會(huì)產(chǎn)生越界錯(cuò)誤。
IDIOM:
for i:=0; i < len(arr1); i++{
arr1[i] = ...
}
也可以使用 for-range 的生成方式:
IDIOM:
for i,_:= range arr1 {
...
}
在這里i也是數(shù)組的索引。當(dāng)然這兩種 for 結(jié)構(gòu)對于切片(slices)(參考 第 7 章)來說也同樣適用。
問題 7.1 下面代碼段的輸出是什么?
a := [...]string{"a", "b", "c", "d"}
for i := range a {
fmt.Println("Array item", i, "is", a[i])
}
Go 語言中的數(shù)組是一種 值類型(不像 C/C++ 中是指向首元素的指針),所以可以通過 new() 來創(chuàng)建: var arr1 = new([5]int)。
那么這種方式和 var arr2 [5]int 的區(qū)別是什么呢?arr1 的類型是 *[5]int,而 arr2的類型是 [5]int。
這樣的結(jié)果就是當(dāng)把一個(gè)數(shù)組賦值給另一個(gè)時(shí),需要在做一次數(shù)組內(nèi)存的拷貝操作。例如:
arr2 := *arr1
arr2[2] = 100
這樣兩個(gè)數(shù)組就有了不同的值,在賦值后修改 arr2 不會(huì)對 arr1 生效。
所以在函數(shù)中數(shù)組作為參數(shù)傳入時(shí),如 func1(arr2),會(huì)產(chǎn)生一次數(shù)組拷貝,func1 方法不會(huì)修改原始的數(shù)組 arr2。
如果你想修改原數(shù)組,那么 arr2 必須通過&操作符以引用方式傳過來,例如 func1(&arr2),下面是一個(gè)例子
示例 7.2 pointer_array.go:
package main
import "fmt"
func f(a [3]int) { fmt.Println(a) }
func fp(a *[3]int) { fmt.Println(a) }
func main() {
var ar [3]int
f(ar) // passes a copy of ar
fp(&ar) // passes a pointer to ar
}
輸出結(jié)果:
[0 0 0]
&[0 0 0]
另一種方法就是生成數(shù)組切片并將其傳遞給函數(shù)(詳見第 7.1.4 節(jié))。
練習(xí)
練習(xí)7.1:array_value.go: 證明當(dāng)數(shù)組賦值時(shí),發(fā)生了數(shù)組內(nèi)存拷貝。
練習(xí)7.2:for_array.go: 寫一個(gè)循環(huán)并用下標(biāo)給數(shù)組賦值(從 0 到 15)并且將數(shù)組打印在屏幕上。
練習(xí)7.3:fibonacci_array.go: 在第 6.6 節(jié)我們看到了一個(gè)遞歸計(jì)算 Fibonacci 數(shù)值的方法。但是通過數(shù)組我們可以更快的計(jì)算出 Fibonacci 數(shù)。完成該方法并打印出前 50 個(gè) Fibonacci 數(shù)字。
如果數(shù)組值已經(jīng)提前知道了,那么可以通過 數(shù)組常量 的方法來初始化數(shù)組,而不用依次使用 []= 方法(所有的組成元素都有相同的常量語法)。
示例 7.3 array_literals.go
package main
import "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}
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
// var arrKeyValue = []string{3: "Chris", 4: "Ron"}
for 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} :這是一個(gè)有 10 個(gè)元素的數(shù)組,除了前三個(gè)元素外其他元素都為 0。
第二種變化:
var arrLazy = [...]int{5, 6, 7, 8, 22}
... 可同樣可以忽略,從技術(shù)上說它們其實(shí)變化成了切片。
第三種變化:key: value syntax
var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}
只有索引 3 和 4 被賦予實(shí)際的值,其他元素都被設(shè)置為空的字符串,所以輸出結(jié)果為:
Person at 0 is
Person at 1 is
Person at 2 is
Person at 3 is Chris
Person at 4 is Ron
在這里數(shù)組長度同樣可以寫成 ... 或者直接忽略。
你可以取任意數(shù)組常量的地址來作為指向新實(shí)例的指針。
示例 7.4 pointer_array2.go
package main
import "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})
}
}
輸出結(jié)果:
&[0 0 0]
&[1 1 1]
&[2 4 8]
幾何點(diǎn)(或者數(shù)學(xué)向量)是一個(gè)使用數(shù)組的經(jīng)典例子。為了簡化代碼通常使用一個(gè)別名:
type Vector3D [3]float32
var vec Vector3D
數(shù)組通常是一維的,但是可以用來組裝成多維數(shù)組,例如:[3][5]int,[2][2][2]float64。
內(nèi)部數(shù)組總是長度相同的。Go 語言的多維數(shù)組是矩形式的(唯一的例外是切片的數(shù)組,參見第 7.2.5 節(jié))。
示例 7.5 multidim_array.go
package main
const (
WIDTH = 1920
HEIGHT = 1080
)
type pixel int
var screen [WIDTH][HEIGHT]pixel
func main() {
for y := 0; y < HEIGHT; y++ {
for x := 0; x < WIDTH; x++ {
screen[x][y] = 0
}
}
}
把一個(gè)大數(shù)組傳遞給函數(shù)會(huì)消耗很多內(nèi)存。有兩種方法可以避免這種現(xiàn)象:
接下來的例子闡明了第一種方法:
示例 7.6 array_sum.go
package main
import "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 array
fmt.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
}
輸出結(jié)果:
The sum of the array is: 24.600000
但這在 Go 中并不常用,通常使用切片(參考 第 7.2 節(jié))。