「快學 Go 語言」第 4 課——低調的數組


「快學 Go 語言」第 4 課——低調的數組


數組就是一篇連續的內存,幾乎所有的計算機語言都有數組,只不過 Go 語言裡面的數組其實並不常用,這是因為數組是定長的靜態的,一旦定義好長度就無法更改,而且不同長度的數組屬於不同的類型,之間不能相互轉換相互賦值,用起來多有不方便之處。

切片是動態的數組,是可以擴充內容增加長度的數組。當長度不變時,它用起來就和普通數組一樣。當長度不同時,它們也屬於相同的類型,之間可以相互賦值。切片的便捷性讓數組的絕大多數應用領域都廣泛地被取代了。

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

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

數組變量的定義

我們先試一下只申明類型,不賦初值。這時編譯器會給數組默認賦上「零值」。數組的零值就是所有內部元素的零值。

<code>package main

import "fmt"

func main() {
\tvar a [9]int
\tfmt.Println(a)
}

------------
[0 0 0 0 0 0 0 0 0]
複製代碼/<code>

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

<code>package main

import "fmt"

func main() {
\tvar a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
\tvar b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
\tc := [8]int{1, 2, 3, 4, 5, 6, 7, 8}
\tfmt.Println(a)
\tfmt.Println(b)
\tfmt.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]
複製代碼/<code>

數組的訪問

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

<code>package main

import "fmt"

func main() {
\tvar squares [9]int
\tfor i := 0; i < len(squares); i++ {
\t\tsquares[i] = (i + 1) * (i + 1)
\t}
\tfmt.Println(squares)
}

--------------------
[1 4 9 16 25 36 49 64 81]
複製代碼/<code>

數組的下標越界檢查(高階知識)

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

<code>package main

import "fmt"

func main() {
\tvar a = [5]int{1,2,3,4,5}
\ta[101] = 255
\tfmt.Println(a)
}

-----
./main.go:7:3: invalid array index 101 (out of bounds for 5-element array)
複製代碼/<code>

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

<code>package main

import "fmt"

func main() {
\tvar a = [5]int{1,2,3,4,5}
\tvar b = 101
\ta[b] = 255
\tfmt.Println(a)
}

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

goroutine 1 [running]:
main.main()
\t/Users/qianwp/go/src/github.com/pyloque/practice/main.go:8 +0x3d
exit status 2
複製代碼/<code>

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

數組賦值

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

<code>package main

import "fmt"

func main() {
\tvar a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
\tvar b [9]int
\tb = a
\ta[0] = 12345
\tfmt.Println(a)

\tfmt.Println(b)
}

--------------------------
[12345 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9]
複製代碼/<code>

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

<code>package main

import "fmt"

func main() {
\tvar a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
\tvar b [10]int
\tb = a
\tfmt.Println(b)
}

--------------------------
./main.go:8:4: cannot use a (type [9]int) as type [10]int in assignment
複製代碼/<code>

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

數組的遍歷

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

<code>package main

import "fmt"

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

------------
0 1
1 2
2 3
3 4
4 5
0 1
1 2
2 3
3 4
4 5
複製代碼/<code>

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


分享到:


相關文章: