[A Tour of Go 學習筆記] 05 陣列和切片
使用官方教學瞭解 Go 語言陣列和切片
初入 Golang 的世界,首先我們使用官方教學:A Tour of Go
來認識基本的 Golang 使用
這篇介紹陣列和切片的概念
[n]T
代表一個擁有n
個T
型別內容的陣列
所以陣列中值型別必須一致,而且長度固定
var a [10]int // a 是一個長度為 10 的純 int 陣列
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
/*
>>> Hello World
>>> [Hello World]
>>> [2 3 5 7 11 13]
*/
這樣感覺起來,陣列超難用的啊
誰會一開始就想好長度
別擔心,Go 提供了另一種方便的方法來操作陣列
切片(slice)
陣列大小固定,而切片則提供動態大小,一般來說比陣列更常使用
[]T
代表一個內容型別是T
的切片,透過下界和上界來取得陣列內容的切片
取得包含 low 但不包含 high 的元素
a[low: high]
以下範例中建立了一個一個a
元素從 1 到 3 的切片
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
/*
>>> [3 5 7]
*/
切片本身不具有存取的功用,是底層陣列的引用
所以變更切片的內容,其他使用同樣底層陣列的切片也會受到影響!
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
/*
>>> [John Paul George Ringo]
>>> [John Paul] [Paul George]
>>> [John XXX] [XXX George]
>>> [John XXX George Ringo]
*/
切片可包含任何類型
,甚至包含其他的切片
package main
import (
"fmt"
"strings"
)
func main() {
// 一個井字遊戲模板
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// 兩個玩家輪流打上 O 和 X
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
/*
>>> X _ X
>>> O _ X
>>> _ _ O
*/
切片表示法
切片聲明法類似於沒有長度的陣列
這是一個陣列聲明
[3]bool{true, true, false}
這是一個切片表示法
會先建立和上面一樣的陣列,然後建立一個指向該陣列的切片
[]bool{true, true, false}
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
/*
>>> [2 3 5 7 11 13]
>>> [true false true true false true]
>>> [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
*/
切片的預設值
切片的預設值是nil
一個nil
切片的長度和容量為 0,並且沒有底層參照陣列
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
/*
>>> [] 0 0
>>> nil!
*/
切片的預設行為
切片預設忽略上限和下限
或是說預設下限是 0 上限是切片長度
假設有一個陣列
var a [10]int
那以下切片內容是一樣的
a[0:10]
a[:10]
a[0:]
a[:]
在底下範例中,因為切片會修改參照的陣列
所以s
本身的內容是會被改變的
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[1:]
fmt.Println(s)
}
/*
>>> [3 5 7]
>>> [3 5]
>>> [5]
*/
切片長度和容量
- 長度:包含的元素個數
- 容量:從第一個元素開始數,到底層參照陣列的最後一個元素的數量
長度可以透過 len(s) 取得
容量可以透過 cap(s) 取得
被切片過改變過長度的陣列,可以透過重新切片來擴充切片
給他足夠的容量,前提是原始陣列長度必須足夠
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
/*
>>> len=6 cap=6 [2 3 5 7 11 13]
>>> len=0 cap=6 []
>>> len=4 cap=6 [2 3 5 7]
>>> len=2 cap=4 [5 7]
*/
但若是超過底層參照的陣列長度會出現 panic
假設初始切片長度為 6,而我們在程式中將切片擴充容量到 9
s := []int{2, 3, 5, 7, 11, 13}
s = s[:9]
printSlice(s)
/*
>>> panic: runtime error: slice bounds out of range [:9] with capacity 6
*/
用 make 建立切片
切片可以用 make
建立,這也是建立動態陣列的方法
make
function 會分配一個內部元素皆為 0 的指定長度陣列,並回傳引用該陣列的切片
如果要指定容量,需向 make
傳入第三個參數
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
/*
>>> a len=5 cap=5 [0 0 0 0 0]
>>> b len=0 cap=5 []
>>> c len=2 cap=5 [0 0]
>>> d len=3 cap=3 [0 0 0]
*/
對切片增加元素
為切片追加新的元素是種經常會使用到的操作
在 golang 中使用Append
來達成,內建方法可以參考:官方文件
append
表示式的第一個參數s
是一個元素類型為T
的切片
其餘類型為T
的值將會被加到原有切片的末尾
func append(s []T, ...T) []T
產生的結果是一個包含元切片所有元素加上新加入元素的切片
若是s
的底層陣列太小,不足以容納所有給定的值的時候
s
會被分派到一個更大的陣列,回傳的切片會指向這個新分配的陣列
(了解更多關於切片的內容,可以閱讀這篇文章:Go Slices: usage and internals
)
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// 加入一個空白的切片
s = append(s, 0)
printSlice(s)
// 切片會照需求增長
s = append(s, 1)
printSlice(s)
// 可以一次性增加多個元素
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
/*
>>> len=0 cap=0 []
>>> len=1 cap=1 [0]
>>> len=2 cap=2 [0 1]
>>> len=5 cap=6 [0 1 2 3 4]
*/