位元詩人 [Golang] 程式設計教學:使用陣列 (Array) 和切片 (Slice)

Golang陣列切片
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在我們先前的程式中,變數代表單一的實體 (entity),然而,我們有時候想要操作多個資料,在電腦程式中,使用各種容器 (collection) 來操作多個資料;透過容器,我們可以更有效率地操作資料而不需要設置一堆變數,也可以透過迴圈走訪容器。在本文中,我們介紹陣列 (array) 和切片 (slice),這兩種容器皆是同質 (homogeneous) 且線性的 (linear)。

陣列 (Array)

陣列是一種同質且線性的容器或資料結構。可以把陣列想到一排藥盒,每個格子中儲存一個資料。陣列的限制在於只能儲存同一種類型的資料,而且建立後長度不能改變。

長度為 6 的陣列

建立陣列

以下實例中建立一個四個元素的字串陣列,然後以索引 (index) 對陣列賦值:

package main

import "log"

func main() {
    var langs [4]string

    langs[0] = "Go"
    langs[1] = "Python"
    langs[2] = "Ruby"
    langs[3] = "PHP"

    if !(langs[0] == "Go") {
        log.Fatal("Wrong string")
    }
}

要注意的是,陣列索引是從 0 開始,而非從 1 開始;在本例中,變數 langs 是一個陣列,長度為 4。

或者,可用較短的語法同時宣告陣列及賦值:

package main

import "log"

func main() {
    langs := [4]string{
        "Go",
        "Python",
        "Ruby",
        "PHP",
    }

    if !(langs[0] == "Go") {
        log.Fatal("Wrong string")
    }
}

走訪陣列

先前我們提過,Go 的 for 迴圈可以使用迭代器 (iterator),迭代器是一種走訪容器的方法,透過迭代器,程式設計者不需要知道容器內部的結構,就可以走訪容器內部的元素。

package main

import "fmt"

func main() {
    langs := [4]string{
        "Go",
        "Python",
        "Ruby",
        "PHP",
    }

    for i, e := range langs {
        fmt.Println(fmt.Sprintf("%d: %s", i+1, e))
    }
}

如果不需要索引,可以使用啞變數代替:

package main

import "fmt"

func main() {
    langs := [4]string{
        "Go",
        "Python",
        "Ruby",
        "PHP",
    }

    for _, e := range langs {
        fmt.Println(e)
    }
}

要注意的是,使用迭代器時,對陣列元素的修改是沒有效果的,如以下實例:

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}

    for _, e := range arr {
        e = e * e
    }

    for _, e := range arr {
        fmt.Println(e)
    }
}

若要修改陣列中的元素,要以索引走訪陣列,再直接修改陣列的元素的值:

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}

    for i := 0; i < len(arr); i++ {
        arr[i] = arr[i] * arr[i]
    }

    for _, e := range arr {
        fmt.Println(e)
    }
}

切片 (Slice)

由於陣列長度在建立後就不能更動,Go 提供切片 (slice) 這種容器。切片和陣列相似,同樣也是線性的、以數字為索引,索引值同樣從 0 開始。

建立切片

以下例子建立一個切片:

package main

import "fmt"

func main() {
    langs := []string{"Go", "Python", "Ruby", "PHP"}

    for _, e := range langs {
        fmt.Println(e)
    }
}

建立切片時,不需預設其長度,因切片會動態改變其長度。

切片也可以由已有的陣列來建立,如下例:

package main

import (
    "fmt"
    "log"
    "reflect"
)

func main() {
    langs := [4]string{"Go", "Python", "Ruby", "PHP"}
    slice := langs[0:4] // Upper bound excluded.

    // Print out the types of these variables
    fmt.Println(reflect.TypeOf(langs))
    fmt.Println(reflect.TypeOf(slice))

    if !(langs[3] == "PHP") {
        log.Fatal("Wrong value")
    }

    slice[3] = "Perl"

    if !(langs[3] == "Perl") {
        log.Fatal("Wrong value")
    }
}

切片的內部,其實也是陣列,切片本身不儲存值,而是儲存到陣列的參考 (reference),簡單地說,切片和陣列內部儲存同一份資料,但透過兩個不同的變數來處理;在我們的這個例子中,我們修改切片的值,原本陣列的值也一併修改了。

我們也可以利用多維切片製作矩陣 (matrix),見下例:

package main

import (
    "log"
)

func main() {
    matrix := [][]float64{
        []float64{1, 2, 3},
        []float64{4, 5, 6},
    }

    if !(matrix[1][1] == 5) {
        log.Fatal("Wrong value")
    }
}

切片也可以在執行期動態產生,這時候會使用 make 做為關鍵字。在以下例子中,我們動態產生一個長度為 5 的切片:

package main

import (
    "fmt"
)

func main() {
    slice := make([]int, 5)

    for i := 0; i < len(slice); i++ {
        n := i + 1
        slice[i] = n * n
    }

    for _, e := range slice {
        fmt.Println(e)
    }
}

走訪切片

先前走訪陣列的方式同樣也可以用在切片上,此處不重覆展示其用法。

改變切片大小

我們在前文中提過,切片長度可以動態改變,這時候會使用 append 函式。在以下例子中,切片的長度由 5 變成 8:

package main

import (
    "log"
)

func main() {
    slice := []int{1, 2, 3, 4, 5}

    if !(len(slice) == 5) {
        log.Fatal("Wrong length")
    }

    slice = append(slice, 6, 7, 8)

    if !(len(slice) == 8) {
        log.Fatal("Wrong length")
    }
}

如果要從切片中移除元素,則需一點小技巧。在本例中,我們移除第三個元素:

package main

import (
    "log"
)

func main() {
    slice := []int{1, 2, 3, 4, 5}

    if !(len(slice) == 5) {
        log.Fatal("Wrong length")
    }

    if !(slice[2] == 3) {
        log.Fatal("Wrong value")
    }

    // Remove the 3rd element.
    slice = append(slice[0:2], slice[3:5]...)

    if !(len(slice) == 4) {
        log.Fatal("Wrong length")
    }

    if !(slice[2] == 4) {
        log.Fatal("Wrong value")
    }
}

其實 Golang 沒有移除元素的函式,我們在取索引時故意略去第三個元素,該元素就被捨去了。

結語

在本文中,我們介紹了陣列和切片兩種容器,兩者皆為線性的容器,以數字做為索引,索引從 0 開始。由於切片可動態改變大小,此外,Go 對切片提供額外的輔助函式,在實務上,切片反而用得比陣列多。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。