《快學 Go 語言》第 4 課 —— 低調的陣列

碼洞發表於2018-11-21

只要掌握了資料結構中的四大法寶,就可以包打天下,他們是:array 、linked list 、hash table、binary tree 。這四大法寶可不是各自為戰的,靈活結合才能遊刃有餘。比如,一個用 hash table 組織的 symbol table,其中個個都是由字元型 array 構成的 linked list 組成的。 --- Go 語言之父 Rob Pike

Go 語言裡面的陣列其實很不常用,這是因為陣列是定長的靜態的,一旦定義好長度就無法更改,而且不同長度的陣列屬於不同的型別,之間不能相互轉換相互賦值,用起來多有不方便之處。

切片是動態的陣列,是可以擴充內容增加長度的陣列。當長度不變時,它用起來就和普通陣列一樣。當長度不同時,它們也屬於相同的型別,之間可以相互賦值。這就決定了陣列的應用領域都廣泛地被切片取代了。

不過也不可以小瞧陣列,在切片的底層實現中,陣列是切片的基石,是切片的特殊語法隱藏了內部的細節,讓使用者不能直接看到內部隱藏的陣列。切片不過是陣列的一個包裝,給頑固的陣列裝上了靈活的翅膀,讓石頭也可以展翅飛翔。

僅僅是上面純文字的說明,讀者肯定會感覺很懵。下面讓我們來看具體的例項。

陣列變數的定義

我們先試一下只申明型別,不賦初值。這時編譯器會給陣列預設賦上「零值」。陣列的零值就是所有內部元素的零值。

package main

import "fmt"

func main() {
    var a [9]int
    fmt.Println(a)
}

------------
[0 0 0 0 0 0 0 0 0]


下面我們看看另外三種變數定義的形式, 效果都是一樣的

package main

import "fmt"

func main() {
    var a = [9]int{123456789}
    var b [10]int = [10]int{12345678910}
    c := [8]int{12345678}
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}

---------------------
[1 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8]

陣列的訪問

接下來我們使用下標來簡單操作一下陣列,這個陣列裡存的是數字的平方值

package main

import "fmt"

func main() {
    var squares [9]int
    for i := 0; i < len(squares); i++ {
        squares[i] = (i + 1) * (i + 1)
    }
    fmt.Println(squares)
}

--------------------
[1 4 9 16 25 36 49 64 81]

陣列的下標越界檢查(高階知識)

上面的程式碼中我們注意到可以使用內建函式 len() 來直接獲取陣列的長度。陣列的長度是編譯期確定的,當我們使用 len() 函式訪問陣列的長度屬性時,編譯器在背後偷偷把它替換成了整數值。

package main

import "fmt"

func main() {
    var a = [5]int{1,2,3,4,5}
    a[101] = 255
    fmt.Println(a)
}

-----
./main.go:7:3: invalid array index 101 (out of bounds for 5-element array)


上面的程式碼執行結果說明了 Go 語言會對陣列訪問下標越界進行編譯器檢查。有一個重要的問題是,如果下標是一個變數,Go 是如何檢查下標越界呢?變數需要在執行時才可以決定是否越界,Go 是如何辦到的呢?

package main

import "fmt"

func main() {
    var a = [5]int{1,2,3,4,5}
    var b = 101
    a[b] = 255
    fmt.Println(a)
}

------------
panic: runtime error: index out of range

goroutine 1 [running]:
main.main()
    /Users/qianwp/go/src/github.com/pyloque/practice/main.go:8 +0x3d
exit status 2


答案是 Go 會在編譯後的程式碼中插入下標越界檢查的邏輯,所以陣列的下標訪問效率是要打折扣的,比不得 C 語言的陣列訪問效能。

陣列賦值

同樣的子元素型別並且是同樣長度的陣列才可以相互賦值,否則就是不同的陣列型別,不能賦值。陣列的賦值本質上是一種淺複製操作,賦值的兩個陣列變數的值不會共享。

package main

import "fmt"

func main() {
    var a = [9]int{123456789}
    var b [9]int
    b = a
    a[0] = 12345
    fmt.Println(a)
    fmt.Println(b)
}

--------------------------
[12345 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9]


從上面程式碼的執行結果中可以看出賦值後兩個陣列並沒有共享內部元素。如果陣列的長度很大,那麼複製操作是有一定的開銷的,使用的時候一定需要注意。下面我們嘗試使用不同長度的陣列賦值會有什麼結果

package main

import "fmt"

func main() {
    var a = [9]int{123456789}
    var b [10]int
    b = a
    fmt.Println(b)
}

--------------------------
./main.go:8:4: cannot use a (type [9]int) as type [10]int in assignment


可以看出不同長度的陣列之間賦值是禁止的,因為它們屬於不同的型別。

陣列的遍歷

陣列除了可以使用下標進行遍歷之外,還可以使用 range 關鍵字來遍歷,range 遍歷提供了下面兩種形式。

package main

import "fmt"

func main() {
    var a = [5]int{1,2,3,4,5}
    for index := range a {
  fmt.Println(index, a[index])
 }
 for index, value := range a {
        fmt.Println(index, value)
    }
}

------------
0 1
1 2
2 3
3 4
4 5
0 1
1 2
2 3
3 4
4 5


考慮到切片的內容太多,我們將獨立一節專門講解切片,下一節將是 Go 語言的極有價值的一節,讀者一定要努力搞清楚每一個細節。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31561269/viewspace-2220960/,如需轉載,請註明出處,否則將追究法律責任。

相關文章