鍍金池/ 教程/ GO/ 7.1 聲明和初始化
4.7 strings 和 strconv 包
13.6 啟動外部命令和程序
?# 11.4 類型判斷:type-switch
12.1 讀取用戶的輸入
10.6 方法
12.2 文件讀寫
13 錯誤處理與測試
9.3 鎖和 sync 包
12.3 文件拷貝
?# 11.7 第一個例子:使用 Sorter 接口排序
?# 11.5 測試一個值是否實現(xiàn)了某個接口
6.4 defer 和追蹤
12.10 XML 數(shù)據(jù)格式
13.10 性能調(diào)試:分析并優(yōu)化 Go 程序
?# 11.1 接口是什么
2.2 Go 環(huán)境變量
2.6 安裝目錄清單
2.5 在 Windows 上安裝 Go
11.11 Printf 和反射
1.2 語言的主要特性與發(fā)展的環(huán)境和影響因素
9.0 包(package)
7.4 切片重組(reslice)
13.2 運行時異常和 panic
10.2 使用工廠方法創(chuàng)建結(jié)構(gòu)體實例
12.8 使用接口的實際例子:fmt.Fprintf
2.4 在 Mac OS X 上安裝 Go
3.8 Go 性能說明
7.2 切片
8.0 Map
3.1 Go 開發(fā)環(huán)境的基本要求
5.6 標簽與 goto
6.10 使用閉包調(diào)試
9.5 自定義包和可見性
4.3 常量
?# 11.2 接口嵌套接口
6.5 內(nèi)置函數(shù)
前言
10.8 垃圾回收和 SetFinalizer
2.8 Go 解釋器
13.7 Go 中的單元測試和基準測試
6.8 閉包
4.9 指針
13.1 錯誤處理
10.1 結(jié)構(gòu)體定義
5.1 if-else 結(jié)構(gòu)
6.6 遞歸函數(shù)
9.9 通過 Git 打包和安裝
2.7 Go 運行時(runtime)
10.7 類型的 String() 方法和格式化描述符
3.7 其它工具
9.6 為自定義包使用 godoc
11.12 接口與動態(tài)類型
13.3 從 panic 中恢復(Recover)
10.3 使用自定義包中的結(jié)構(gòu)體
11.14 結(jié)構(gòu)體、集合和高階函數(shù)
3.6 生成代碼文檔
9.2 regexp 包
4.1 文件名、關(guān)鍵字與標識符
?# 11.6 使用方法集與接口
7.0 數(shù)組與切片
7.1 聲明和初始化
12.11 用 Gob 傳輸數(shù)據(jù)
5.5 Break 與 continue
1.1 起源與發(fā)展
?# 11 接口(Interfaces)與反射(reflection)
6.9 應用閉包:將函數(shù)作為返回值
4.2 Go 程序的基本結(jié)構(gòu)和要素
8.6 將 map 的鍵值對調(diào)
6.11 計算函數(shù)執(zhí)行時間
5.0 控制結(jié)構(gòu)
10.5 匿名字段和內(nèi)嵌結(jié)構(gòu)體
4.6 字符串
3.0 編輯器、集成開發(fā)環(huán)境與其它工具
13.8 測試的具體例子
7.6 字符串、數(shù)組和切片的應用
8.4 map 類型的切片
3.9 與其它語言進行交互
7.3 For-range 結(jié)構(gòu)
9.7 使用 go install 安裝自定義包
6.0 函數(shù)
9.8 自定義包的目錄結(jié)構(gòu)、go install 和 go test
6.3 傳遞變長參數(shù)
13.9 用(測試數(shù)據(jù))表驅(qū)動測試
11.9 空接口
8.1 聲明、初始化和 make
6.2 函數(shù)參數(shù)與返回值
9.11 在 Go 程序中使用外部庫
3.3 調(diào)試器
4.5 基本類型和運算符
?# 11.8 第二個例子:讀和寫
12.5 用 buffer 讀取文件
總結(jié):Go 中的面向?qū)ο?/span>
11.10 反射包
12.7 用 defer 關(guān)閉文件
9.4 精密計算和 big 包
4.4 變量
6.1 介紹
13.4 自定義包中的錯誤處理和 panicking
12.4 從命令行讀取參數(shù)
9.10 Go 的外部包和項目
8.3 for-range 的配套用法
3.5 格式化代碼
10.4 帶標簽的結(jié)構(gòu)體
7.5 切片的復制與追加
?# 11.3 類型斷言:如何檢測和轉(zhuǎn)換接口變量的類型
5.4 for 結(jié)構(gòu)
4.8 時間和日期
2.3 在 Linux 上安裝 Go
12 讀寫數(shù)據(jù)
6.12 通過內(nèi)存緩存來提升性能
9.1 標準庫概述
12.6 用切片讀寫文件
10 結(jié)構(gòu)(struct)與方法(method)
8.5 map 的排序
12.9 JSON 數(shù)據(jù)格式
13.5 一種用閉包處理錯誤的模式
3.2 編輯器和集成開發(fā)環(huán)境
12.12 Go 中的密碼學
5.2 測試多返回值函數(shù)的錯誤
6.7 將函數(shù)作為參數(shù)
8.2 測試鍵值對是否存在及刪除元素
3.4 構(gòu)建并運行 Go 程序
2.1 平臺與架構(gòu)
5.3 switch 結(jié)構(gòu)

7.1 聲明和初始化

7.1.1 概念

數(shù)組是具有相同 唯一類型 的一組已編號且長度固定的數(shù)據(jù)項序列(這是一種同構(gòu)的數(shù)據(jù)結(jié)構(gòu));這種類型可以是任意的原始類型例如整型、字符串或者自定義類型。數(shù)組長度必須是一個常量表達式,并且必須是一個非負整數(shù)。數(shù)組長度也是數(shù)組類型的一部分,所以[5]int和[10]int是屬于不同類型的。數(shù)組的編譯時值初始化是按照數(shù)組順序完成的(如下)。

注意事項 如果我們想讓數(shù)組元素類型為任意類型的話可以使用空接口作為類型(參考 第 11 章)。當使用值時我們必須先做一個類型判斷(參考 第 11 章)。

數(shù)組元素可以通過 索引(位置)來讀?。ɑ蛘咝薷模?,索引從 0 開始,第一個元素索引為 0,第二個索引為 1,以此類推。(數(shù)組以 0 開始在所有類 C 語言中是相似的)。元素的數(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="" />

每個元素是一個整型值,當聲明數(shù)組時所有的元素都會被自動初始化為默認值 0。

arr1 的長度是 5,索引范圍從 0 到 len(arr1)-1。

第一個元素是 arr1[0],第三個元素是 arr1[2];總體來說索引 i 代表的元素是 arr1[i],最后一個元素是 arr1[len(arr1)-1]

對索引項為 i 的數(shù)組元素賦值可以這么操作:arr[i] = value,所以數(shù)組是 可變的。

只有有效的索引可以被使用,當使用等于或者大于 len(arr1) 的索引時:如果編譯器可以檢測到,會給出索引超限的提示信息;如果檢測不到的話編譯會通過而運行時會 panic:(參考 第 13 章

runtime error: index out of range

由于索引的存在,遍歷數(shù)組的方法自然就是使用 for 結(jié)構(gòu):

  • 通過 for 初始化數(shù)組項
  • 通過 for 打印數(shù)組元素
  • 通過 for 依次處理元素

示例 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) 的話會產(chǎn)生越界錯誤。

IDIOM:

for i:=0; i < len(arr1); i++{
    arr1[i] = ...
}

也可以使用 for-range 的生成方式:

IDIOM:

for i,_:= range arr1 {
...
}

在這里i也是數(shù)組的索引。當然這兩種 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é)果就是當把一個數(shù)組賦值給另一個時,需要在做一次數(shù)組內(nèi)存的拷貝操作。例如:

arr2 := *arr1
arr2[2] = 100

這樣兩個數(shù)組就有了不同的值,在賦值后修改 arr2 不會對 arr1 生效。

所以在函數(shù)中數(shù)組作為參數(shù)傳入時,如 func1(arr2),會產(chǎn)生一次數(shù)組拷貝,func1 方法不會修改原始的數(shù)組 arr2。

如果你想修改原數(shù)組,那么 arr2 必須通過&操作符以引用方式傳過來,例如 func1(&arr2),下面是一個例子

示例 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é))。

練習

練習7.1:array_value.go: 證明當數(shù)組賦值時,發(fā)生了數(shù)組內(nèi)存拷貝。

練習7.2:for_array.go: 寫一個循環(huán)并用下標給數(shù)組賦值(從 0 到 15)并且將數(shù)組打印在屏幕上。

練習7.3:fibonacci_array.go: 在第 6.6 節(jié)我們看到了一個遞歸計算 Fibonacci 數(shù)值的方法。但是通過數(shù)組我們可以更快的計算出 Fibonacci 數(shù)。完成該方法并打印出前 50 個 Fibonacci 數(shù)字。

7.1.2 數(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} :這是一個有 10 個元素的數(shù)組,除了前三個元素外其他元素都為 0。

第二種變化:

var arrLazy = [...]int{5, 6, 7, 8, 22}

... 可同樣可以忽略,從技術(shù)上說它們其實變化成了切片。

第三種變化:key: value syntax

var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}

只有索引 3 和 4 被賦予實際的值,其他元素都被設(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ù)組常量的地址來作為指向新實例的指針。

示例 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]

幾何點(或者數(shù)學向量)是一個使用數(shù)組的經(jīng)典例子。為了簡化代碼通常使用一個別名:

type Vector3D [3]float32
var vec Vector3D

7.1.3 多維數(shù)組

數(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
        }
    }
}

7.1.4 將數(shù)組傳遞給函數(shù)

把一個大數(shù)組傳遞給函數(shù)會消耗很多內(nèi)存。有兩種方法可以避免這種現(xiàn)象:

  • 傳遞數(shù)組的指針
  • 使用數(shù)組的切片

接下來的例子闡明了第一種方法:

示例 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é))。

鏈接