前言
在我們先前的程式中,變數代表單一的實體 (entity),然而,我們有時候想要操作多個資料,在電腦程式中,使用各種容器 (collection) 來操作多個資料;透過容器,我們可以更有效率地操作資料而不需要設置一堆變數,也可以透過迴圈走訪容器。在本文中,我們介紹陣列 (array) 和切片 (slice),這兩種容器皆是同質 (homogeneous) 且線性的 (linear)。
陣列 (Array)
陣列是一種同質且線性的容器或資料結構。可以把陣列想到一排藥盒,每個格子中儲存一個資料。陣列的限制在於只能儲存同一種類型的資料,而且建立後長度不能改變。
建立陣列
以下實例中建立一個四個元素的字串陣列,然後以索引 (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 對切片提供額外的輔助函式,在實務上,切片反而用得比陣列多。