# **多維數組**
數組可以有多個維度。然而,在沒有嚴肅理由的情況下,使用超過三個維度會使你的程序難于閱讀,并可能產生bug。
>Tip: 數組可以存儲所有類型的元素,這里我們只用整數講解,因為他們更容易理解和類型。
下面的Go代碼演示了如何創建一個二維數組(twoD)和另一個三維數組(threeD):
```go
twoD := [4][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12},{13, 14, 15, 16}}
threeD := [2][2][2]int{{{1, 0}, {-2, 4}}, {{5, -1}, {7, 0}}}
```
訪問、分配或打印這兩個數組中的一個元素是很容易的。例如,twoD數組的第一個元素是`twoD[0][0]`,它的值是1。
因此,使用多層循環遍歷threeD 的每一個元素可以通過下面的代碼實現:
```GO
for i := 0; i < len(threeD); i++ {
for j := 0; j < len(v); j++ {
for k := 0; k < len(m); k++ {
}
}
}
```
我們看到,我們需要和數組維度一樣的循環的數量來遍歷所有元素。這個規則對切片`slice`也適用,下章將會講到。這里使用`x, y, z`替代`i, j, k`會比較好。
`usingArrays.go`展示了如何在Go中使用數組,下面分成三部分進行講解。
第一部分的代碼如下:
```Go
package main
import (
"fmt"
)
func main() {
anArray := [4]int{1, 2, 4, -4}
twoD := [4][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14,15, 16}}
threeD := [2][2][2]int{{{1, 0}, {-2, 4}}, {{5,-1},{7, 0}}}
```
這里我們定義了三個數組變量分別叫 anArray, twoD 和 threeD。
第二部分的代碼如下:
```go
fmt.Println("The length of", anArray, "is", len(anArray))
fmt.Println("The first element of", twoD, "is", twoD[0][0])
fmt.Println("The length of", threeD, "is", len(threeD))
for i := 0; i < len(threeD); i++ {
v := threeD[i]
for j := 0; j < len(v); j++ {
m := v[j]
for k := 0; k < len(m); k++ {
fmt.Print(m[k], " ")
}
}
fmt.Println()
}
```
我們從第一個for循環中得到一個二維數組(`threeD[i]`), 同理,第二個for循環中得到一個一維數組。最后一個for循環遍歷得到的一維數組得到里面的每一個元素。
最后一部分代碼:
The last code part comes with the next Go code:
```go
for _, v := range threeD {
for _, m := range v {
for _, s := range m {
fmt.Print(s, " ")
}
}
fmt.Println()
}
}
```
`range`關鍵字的作用與上一個代碼段的for循環中使用的迭代變量完全相同,但它的作用更優雅、更清晰。然而,如果想要提前知道將要執行的迭代次數,那么就不能使用`range`關鍵字。
>`range`關鍵字也適用于Go映射,這使得它非常方便,也是我喜歡的迭代方式。正如將在第9章中看到的Go - Goroutines、channel和pipeline中的并發性,range關鍵字也適用于channel。
執行 `usingArrays.go` 結果輸出如下:
```bash
$ go run usingArrays.go
The length of [1 2 4 -4] is 4
The first element of [[1 2 3 4] [5 6 7 8] [9 10 11 12] [13 14 15 16]] is 1
The length of [[[1 0] [-2 4]] [[5 -1] [7 0]]] is 2
1 0 -2 4
5 -1 7 0
1 0 -2 4
5 -1 7 0
```
數組最大的問題之一是越界錯誤,這意味著試圖訪問數組中不存在的元素。這就像試圖訪問一個只有5個元素的數組的第6個元素。Go編譯器將此認作編譯檢測的編譯問題,因為這有助于開發工作流。因此,Go編譯器能檢測數組越界錯誤并給出如下錯誤提示:
```bash
./a.go:10: invalid array index -1 (index must be non-negative)
./a.go:10: invalid array index 20 (out of bounds for 2-element array)
```
- 介紹
- 1 Go與操作系統
- 01.1 Go的歷史
- 01.2 Go的未來
- 01.3 Go的優點
- 01.3.1 Go是完美的么
- 01.3.2 什么是預處理器
- 01.3.3 godoc
- 01.4 編譯Go代碼
- 2 理解 Go 的內部構造
- Go 編譯器
- Go 的垃圾回收
- 三色算法
- 有關 Go 垃圾收集器操作的更多信息
- Maps, silces 與 Go 垃圾回收器
- Unsafe code
- 有關 unsafe 包
- 另一個 usafe 包的例子
- 從 Go 調用 C 代碼
- 在同一文件用 Go 調用 C 代碼
- 在單獨的文件用 Go 調用 C 代碼
- 從 C 調用 Go 代碼
- Go 包
- C 代碼
- defer 關鍵字
- 用 defer 打印日志
- Panic 和 Recover
- 單獨使用 Panic 函數
- 兩個好用的 UNIX 工具
- strace
- dtrace
- 配置 Go 開發環境
- go env 命令
- Go 匯編器
- 節點樹
- 進一步了解 Go 構建
- 創建 WebAssembly 代碼
- 對 Webassembly 的簡單介紹
- 為什么 WebAssembly 很重要
- Go 與 WebAssembly
- 示例
- 使用創建好的 WebAssembly 代碼
- Go 編碼風格建議
- 練習和相關鏈接
- 本章小結
- 3 Go基本數據類型
- 03.1 Go循環
- 03.1.1 for循環
- 03.1.2 while循環
- 03.1.3 range關鍵字
- 03.1.4 for循環代碼示例
- 03.3 Go切片
- 03.3.1 切片基本操作
- 03.3.2 切片的擴容
- 03.3.3 字節切片
- 03.3.4 copy()函數
- 03.3.5 多維切片
- 03.3.6 使用切片的代碼示例
- 03.3.7 使用sort.Slice()排序
- 03.4 Go 映射(map)
- 03.4.1 Map值為nil的坑
- 03.4.2 何時該使用Map?
- 03.5 Go 常量
- 03.5.1 常量生成器:iota
- 03.6 Go 指針
- 03.7 時間與日期的處理技巧
- 03.7.1 解析時間
- 03.7.2 解析時間的代碼示例
- 03.7.3 解析日期
- 03.7.4 解析日期的代碼示例
- 03.7.5 格式化時間與日期
- 03.8 延伸閱讀
- 03.9 練習
- 03.10 本章小結
- 9 并發-Goroutines,Channel和Pipeline
- 09.1 關于進程,線程和Go協程
- 09.1.1 Go調度器
- 09.1.2 并發與并行
- 09.2 Goroutines
- 09.2.1 創建一個Goroutine
- 09.2.2 創建多個Goroutine
- 09.3 優雅地結束goroutines
- 09.3.1 當Add()和Done()的數量不匹配時會發生什么?
- 09.4 Channel(通道)
- 09.4.1 通道的寫入
- 09.4.2 從通道接收數據
- 09.4.3 通道作為函數參數傳遞
- 09.5 管道
- 09.6 延展閱讀
- 09.7 練習
- 09.8 本章小結