Byte Ebi's Logo

Byte Ebi 🍤

每天一小口,蝦米變鯨魚

[A Tour of Go 學習筆記] 05 陣列和切片

使用官方教學瞭解 Go 語言陣列和切片

Ray

初入 Golang 的世界,首先我們使用官方教學:A Tour of Go 來認識基本的 Golang 使用
這篇介紹陣列和切片的概念

[n]T 代表一個擁有nT型別內容的陣列
所以陣列中值型別必須一致,而且長度固定

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]
*/

最新文章

Category

Tag