跳至主要内容

[Golang] Array and Slice

golang

在 Go 裡面有兩種資料結構可以處理包含許多元素的清單資料,分別是 ArraySlice

  • Array:清單的長度是固定的(fixed length),屬於原生型別(primitive type),較少在程式中使用。
  • Slice:可以增加或減少清單的長度,使用 [] 定義,例如,[]byte 是 byte slice,指元素為 byte 的 slice;[]string 是 string slice,指元素為 string 的 slice。

不論是 Array 或 Slice,它們內部的元素都必須要有相同的資料型別(字串、數值、...)

這裡的「陣列」一般指稱的是可變動長度的 Slice。

TL;DR

  • slice 背後的 array 不夠放時,append 後會建立一個新的 array
  • slice 就像窗戶(window),只能透過這個窗戶看到部分的 array
  • 複製 slice,或從某一個 slice 產生新的 slice,概念就像是產生了一個新的窗戶

Arrays 的建立

基本

在 Array 中陣列的元素是固定的:

// 陣列型別:[n]T
// 陣列的元素只能有 10 個,且都是數值

func main() {
// 方式一:先定義再賦值
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a) // [Hello World]

// 方式二
var people [4]int // len=4 cap=4,[0,0,0,0]
people = [4]int{10,20,30,40} // len=4 cap=4,[10,20,30,40]

// 方式三:定義且同時賦值
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes) // [2 3 5 7 11 13]
}

使用 ... 根據元素數目建立 Array

使用 [...]T{} 可以根據元素的數目自動建立陣列:

// 沒有使用 ...,建立出來的會是 slice
arr := []string{"North", "East", "South", "West"}
fmt.Println(reflect.TypeOf(arr).Kind(), len(arr)) // slice 4

// 使用 ...,建立出來的會是 array
arrWithDots := [...]string{"North", "East", "South", "West"}
fmt.Println(reflect.TypeOf(arrWithDots).Kind(), len(arrWithDots)) // array 4

How do you determine if a variable is a slice or array? @ StackOverflow

Slice 的建立

基本

// slice 型別:[]T
// slice := make([]T, len, cap)

func main() {
// 方式一:建立一個帶有資料的 string slice,適合用在知道 slice 裡面的元素有哪些時
people := []string{"Aaron", "Jim", "Bob", "Ken"}

// 方式二:透過 make 可以建立「空 slice」,適合用會對 slice 中特定位置元素進行操作時
people := make([]int, 4) // len=4 cap=4,[0,0,0,0]

// 方式三:空的 slice,一般會搭配 append 使用
var people []string

// 方式四:大該知道需要多少元素時使用,搭配 append 使用
people := make([]string, 0, 5) // len=0 cap=5, []
}

實際上當我們在 Go 建立 slice 時,它內部會建立兩個不同的資料結構,分別是 slice 和 array。

  • slice 的 zero value 是 nil,而 nil 的 slice 其 lencap 都是 0

根據 Array 產生 Slice

可以把 Slice 想成 Window,實際上看到的東西還是 Array,但透過 Slice 這個 window 只能看到部分的 Array

arr := [5]int{1, 2, 3, 4, 5}

// 根據這個 Array 產生 Slice
sliceVersion := arr[:]
fmt.Println(sliceVersion) // [1 2 3 4 5]

partialSlice := arr[2:5]
fmt.Println(partialSlice) // [3 4 5]
提示

slice 只存了能夠連結到底層 array 的 pointer,在思考的時候,要把 "slice" 當成一個遮罩或視窗(window),透過它過濾原本 array 的內容後,看到的內容才是 slice 的結果。

capacity and length

在 Slice 中會包含

  • Pointer to Array:這個 pointer 會指向實際上在底層的 array。
  • Capacity:從 slice 的第一個元素開始算起,它底層 array 的元素數目
  • Length:該 slice 中的元素數目

Imgur

它們會如下圖存放在記憶體中,因此當我們使用該 slice 時,它會先指向該 slice,而該 slice 中的 pointer to head 會在指到底層的 array:

Imgur

變更 Slice 的 length 和 capacity

slice 的 lengthcapacity 可以變更:

func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]

s = s[:0]
printSlice(s) // len=0 cap=6 []

s = s[:4]
printSlice(s) // len=4 cap=6 [2 3 5 7]

s = s[2:]
printSlice(s) // len=2 cap=4 [5 7]
}

func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

擴展 slice

func main() {
s := []int{1, 2, 3, 4, 5}
newSlice := s[1:3] // [2,3], len = 5, cap = 5

// 沒有指定 end index 並不會 expand slice
nonExpandedSlice := newSlice[0:] // [2, 3]

// 如果想要 expanded slice 需要指定 end index 才行
expandedSlice := newSlice[0:4] // [2, 3, 4, 5]
}
警告

slice expand 只能向右,不能向左,也就是一但左邊的元素被切掉後,就拿不回來。

使用 make 建立 slice 來指定 len 和 cap

使用 make 建立 slice 時,可以指定該 slice 的 length 和 capacity:

func main() {
// make(T, length, capacity)
a := make([]int, 5) // len(a)=5, cap(a)=5, [0 0 0 0 0]

// 建立特定 capacity 的 slice
b := make([]int, 0, 5) // len=0 cap=5, []
b = b[:cap(b)] // len=5 cap=5, [0 0 0 0 0]
b = b[1:] // len=4 cap=4, [0 0 0 0]
}

若 length 的數量不足時,將無法將元素放入 slice 中,這時候可以使用 append 來擴展 slice:

// slice 的 length 不足時無法填入元素
func main() {
scores := make([]int, 0, 10)
fmt.Println(len(scores), cap(scores)) // 0, 10
//scores[7] = 9033 // 無法填入元素,因為 scores 的 length 不足

// 這時候可使用 append 來擴展 slice
scores = append(scores, 5)
fmt.Println(scores)
fmt.Println(len(scores), cap(scores)) // 1, 10

// 但要達到原本的目的,需要使用切割 slice
scores = scores[0:8]
fmt.Println(len(scores), cap(scores)) // 8, 10
scores[7] = 9033
fmt.Println(scores) // [5 0 0 0 0 0 0 9033]
}

zero value

slice 的 zero value 是 nil,也就是當一個 slice 的 length, capacity 都是 0,而且沒有底層 array 時:

// slice 的 zero value 是 nil
// 也就是當一個 slice 的 length 和 capacity 都是 0,並且沒有底層的 array 時

func main() {
var s []int
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) // len=0 cap=0 []

if s == nil {
fmt.Println("nil!") // nil!
}
}

slice of integer, boolean, struct

func main() {
// integer slice
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)

// boolean slice
r := []bool{ true, false, true, true, false, true}
fmt.Println(r)

// struct slice
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}

建立多維度的 slice(slices of slices)

Slices 裡面可以包含任何型別,包括 slice 型別,如此就可以建立多維度的 slice:

// 建立多維度的空陣列
func main() {
arr := make([][]int, 3)
fmt.Println(arr) // [[] [] []]

// 賦值
arr[0] = []int{1}
arr[1] = []int{2}
arr[2] = []int{3}
fmt.Println(arr) // [[1] [2] [3]]
}

// 建立多維度的陣列並同時賦值
func main() {
arr := [][]int{
[]int{1},
[]int{2},
}
fmt.Println(arr) // [[1] [2]]
}

或更複雜的:

func main() {
board := [][]string{
[]string{"-", "-", "-"},
[]string{"-", "-", "-"},
[]string{"-", "-", "-"},
}
fmt.Println(board) // [[- - -] [- - -] [- - -]]

board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
fmt.Println(board) // [[X - X] [O - X] [- - O]]

//X - X
//O - X
//- - O
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}

Slice 的操作與疊代

func main() {
// 定義內部元素型別為字串的陣列
fruits := []string{"apple", "banana", "grape", "orange"}

// 取得 slice 的 length 和 capacity
fmt.Println(len(fruits)) // length, 4
fmt.Println(cap(fruits)) // capacity, 4

// append 為陣列添加元素(不會改變原陣列)
fruits = append(fruits, "strawberry")

// range syntax: fruits[start:end] 可以截取陣列,從 "start" 開始到 "end-1" 的元素
fmt.Println(fruits[0:2]) // [apple, banana]
fmt.Println(fruits[:3]) // [apple, banana, grape]
fmt.Println(fruits[2:]) // [grape, orange, strawberry]

// 透過 range cards 可以疊代陣列元素
for _, fruit := range fruits {
fmt.Println(fruit)
}
}

在疊代陣列時會使用到 for i, card := range cards{…},其中如果 i 在疊代時沒有被使用到,但有宣告它時,程式會噴錯;因此如果不需要用到這個變數時,需把它命名為 _

golang

切割(:)

須要留意的是,在 golang 中使用 : 來切割 slice 時,並不會複製原本的 slice 中的資料,而是建立一個新的 slice,但實際上還是指稱到相同位址的底層 array(並沒有複製新的),因此還是會改到原本的元素

func main() {
scores := []int{1, 2, 3, 4, 5}
newSlice := scores[2:4]
fmt.Println(newSlice) // 3, 4
newSlice[0] = 999 // 把原本 scores 中 index 值為 3 的元素改成 999
fmt.Println(scores) // 1, 2, 999, 4, 5
}
warning

有時我們只需要使用原本 slice 中的一小部分元素,但由於透過 : 的方式重新分割 slice 時,該 slice 仍然會指稱到底層相同的 array,這導致雖然我們只用了原本 slice 中的一小部分元素,但其他多餘的部分因為仍然被保留在底層的 array 中,進而使得該 array 無法被 garbage collection。因此若只需要使用原本 slice 中一小部分的元素內容時,建議可以使用 copyappend 建立新的 slice 與其對應的新的 array

另外,使用 : 時,不能超過它本身的 capacity,否則會導致 runtime panic:

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

// over the slice capacity will cause runtime panic
overSliceCap := s[:6]
fmt.Println(overSliceCap)
}

由於無法直接透過 : 來擴充 slice 的 capacity,因此若有需要擴充原 slice 的 capacity 時,需要透過 appendcopy 的方式來擴充原本 slice 的 capacity。

append

使用 append 可以擴充 slice 底層 array 的 capacity:

// func append(s []T, x ...T) []T

func main() {
var s []int
printSlice(s) // len=0 cap=0 []


// 一次添加一個元素
s = append(s, 4)
printSlice(s) // len=3 cap=4 [2 3 4]

// 一次添加多個元素
s = append(s, 2, 3)
printSlice(s) //len=2 cap=2 [2 3]
}

append 也可以把另一個 slice append 進到原本的 slice 中。使用 append(a, b...) 就可以把 b 這個 slice append 到 a 這個 slice 中:

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

s = append(s, b...) // 等同於 append(s, b[0], b[1], b[2])
fmt.Println(s) // [1 2 3 4 5]
}

需要特別留意,在底層 array 的 capacity 已經滿的情況下,append 它會創造一個更大的陣列,並且複製原本的值到新陣列中,這時候,這兩個 slice 背後會參照到的 array 是不同的。

舉例來說,在 capacity 還足夠,沒有觸發建立新的 array 時:

// 如果 append 沒有觸發建立新的 array,則這兩個 slice 仍會參照到相同的 array
func main() {
arr := [3]int{1, 2, 3} // 建立一個 capacity 為 3 的 array
slice := arr[0:2] // 建立 len 為 2,cap 為 3 的 slice,[1, 2]
newSlice := append(slice, 3) // 因為 cap 是 3,所以 append 一個元素並不會建立新的 array

// 當改變 slice 中的第一個元素時,會同時反映到兩個 slice 上
slice[0] = 999
fmt.Println(slice) // [999, 2]
fmt.Println(newSlice) // [999, 2, 3]
}

但如果 capacity 不夠,會觸發建立新的 array 時:

func main() {
// 如果 append 觸發建立新的 array,則這兩個 slice 會參照到不同的 array
slice := []int{1, 2, 3} // 建立一個 len 為 3,cap 為 3 的 slice
newSlice := append(slice, 4) // 因為 slice 的 cap 是 3,所以 append 後會產生新的 array

// 當改變 slice 中的第一個元素時,不會反映到新建立的 slice(因為參照到的 array 不同)
slice[0] = 999
fmt.Println(slice) // [999, 2, 3]
fmt.Println(newSlice) // [1, 2, 3, 4]
}

為了避免兩個 slice 實際上卻參照到不同的底層 array,使得以為有改值,卻沒有真的改到的問題產生,一般來說,使用 append 時會把新的 slice 指派會舊的 slice 變數,也就是:

slice := []int{1, 2, 3}

// 使用 append 後會指派會原本 slice 的變數,以避免指稱到兩個不同的底層 array 而有無預期到的 bug 產生
slice = append(slice, 4)
信息

cap 的數量再擴展時會是 1, 2, 4, 8, 16, ....

copy

func copy(dst, src []T) int
  • 回傳的值是複製了多少元素(the number of elements copied)

範例:

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

// STEP 1:建立空 slice 且長度為 4
cloneScores := make([]int, 4)

// STEP 2:使用 copy 將前 scores 中的前三個元素複製到 cloneScores 中
copy(cloneScores, scores[:len(scores)-2])
fmt.Println(cloneScores) // [1,2,3, 0]
}
  • cloneScores 的元素數量如果「多於」被複製進去的元素時,會用 zero value 去補。例如,當 cloneScores 的長度是 4,但只複製 3 個元素進去時,最後位置多出來的元素會補 zero value。
  • cloneScores 的元素數量如果「少於」被複製進去的元素時,超過的元素不會被複製進去。例如,當 cloneScores 的長度是 1,但卻複製了 3 個元素進去時,只會有 1 個元素被複製進去。

使用 copy 可以用來擴展擴展 slice 的 capacity。在沒有 copy 時,寫法會像這樣:

// Growing slices
// https://blog.golang.org/slices-intro

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
t[i] = s[i]
}
s = t

使用 copy 的話可以簡化成這樣:

// s is another slice
t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

range

  • 如果只需要 indexfor i := range pow
  • 如果只需要 valuefor _, value := range pwd
func main() {
pow := []int {1, 2, 4, 8, 16, 32, 64, 128}

for i, v := range pow {
fmt.Printf("2**%d = %d\n", i , v)
}
}

Byte Slice

Byte Slice 其實本質上就是字串的另一種表示方式,它只是將字串轉換成電腦比較好理解的方式。

例如字串 "Hi there!" 轉換成 byte slice 後,就是將這些單字轉換成十進位 的 ASCII 碼:

golang

Utils

Filter

參考資料

Golang 中沒有內建 filter, includes, some 等這類方法,需要自己實作:

filter
func filter[T any](slice []T, f func(T) bool) []T {
var filtered []T

for _, element := range slice {
if f(element) {
filtered = append(filtered, element)
}
}

return filtered
}
includes
func includes[T any](slice []T, f func(T) bool) bool {
for _, element := range slice {
if f(element) {
return true
}
}
return false
}

移除最後一個元素

func main() {
scores := []int{1, 2, 3, 4, 5}
removeLastIndex := scores[:len(scores)-1]

fmt.Println(removeLastIndex) // [1,2,3,4]
}

移除特定 index 的元素

透過下面的方式可以移除 slice 中特定位置的元素,但 slice 中元素的順序會改變:

func main() {
scores := []int{1, 2, 3, 4, 5}
scores = removeAtIndex(scores, 2)
fmt.Println(scores) // [1, 2, 5, 4]
}

func removeAtIndex(source []int, index int) []int {
// STEP 1:取得最後一個元素的 index
lastIndex := len(source) - 1

// STEP 2:把要移除的元素換到最後一個位置
source[index], source[lastIndex] = source[lastIndex], source[index]

// STEP 3:除了最後一個位置的元素其他回傳出去
return source[:lastIndex]
}

Advanced

使用 : 取出的會是 reference

參考:GO 程式設計模式:切片,介面,時間和性能

// 參考:https://coolshell.cn/articles/21128.html

func main() {
foo := make([]int, 5)
foo[3] = 42
foo[4] = 100

// 使用 `:` 時 foo 和 bar 仍會參照到底層相同的陣列(foo 和 bar 共享相同的記憶體)
// 因此修改 bar 即會修改到 foo
bar := foo[1:4]
bar[1] = 99
fmt.Println(bar) // [0 99 42]
fmt.Println(foo) // [0 0 99 42 100]
}

append 有機會產生指稱到不同的 slice

以同樣的例子來說,剛剛 foobar 指稱到底層相同的 array,但若這時後因為使用了 append 讓原本的 foo 因為 capacity 不夠而需要擴充時,這時候會產生 relocate 的情況,這時候將使得 append 後的 foo 和原本的 foo 指稱到的會是底層不同的記憶體位置:

func main() {
foo := make([]int, 5)
foo[3] = 42
foo[4] = 100

bar := foo[1:4]

// 當 foo 超過原本的 capacity 時,array 會 relocate
// 這個 foo 和原本 bar 切割出來的 foo 指稱到的已經是底層不同的 array
foo = append(foo, 0)

bar[1] = 99
fmt.Println(bar) // [0 99 42]
fmt.Println(foo) // [0 0 0 42 100 0]
}
warning

當 slice 在 append 時若 capacity 不足,會重新分配(relocate)記憶體,會有新的 array 被建立,以擴大 capacity,進而導致,append 前後的 slice 會指稱到底層不同陣列。

append 若沒有重新 relocate 還是會指稱到相同的位置

這是一個很神奇的例子:

// 程式來源:https://coolshell.cn/articles/21128.html
func main() {
path := []byte("AAAA/BBBBBBBBB")
sepIndex := bytes.IndexByte(path, '/')
fmt.Println(sepIndex) // 4

dir1 := path[:sepIndex] // len 4, cap: 14
dir2 := path[sepIndex+1:] // len 9, cap: 9

fmt.Println("dir1 =>", string(dir1), cap(dir1)) // AAAA
fmt.Println("dir2 =>", string(dir2), cap(dir2)) // BBBBBBBBB

dir1 = append(dir1, "suffix"...)
fmt.Println("dir1 =>", string(dir1)) // AAAAsuffix
fmt.Println("dir2 =>", string(dir2)) // uffixBBBB
}

從上面這段程式碼中可以看到,透過切割([low:high])的方式,產生了 dir1dir2,當我們使用切割時,被切割出來的 slice 預設的 capacity 會是從 low 開始到最後,也就是 cap(input) - low,因此 dir1 的 capacity 會是 14,dir2 的 capacity 會是 9。

這時候若針對 dir1 使用 append 且沒有超過其 capacity 時,dir1 和 dir2 實際上仍指稱到底層相同的 array,進而導致雖然是針對 dir1 使用 append,但卻同時改到了 dir2 的值:

        01234567890123
path AAAA/BBBBBBBBB
dir1 AAAA
dir2 BBBBBBBBB
--- append 後 ---
01234567890123
dir1 AAAAsuffix
dir2 uffixBBBB

要解決這個問題可以在切割時使用 Full slice expressions。full slice expression 指的是在切割時使用:

input[low:high:max]

以此方式切割出來的 slice 其 capacity 會是 max-low,因此當我們把 dir1 改成:

dir1 := path[:sepIndex:sepIndex]  // len: 4, cap: 4

由於 dir1 的 capacity 只有 4,因此在執行 append 時勢必會使得記憶體 relocate,最後 dir1dir2 就會指稱到底層不同的 array 而不會有互相影響的情況。

把 slice 當成 function 的參數時

當我們在 func 中把 slice 當成參數傳入時,它仍然還是使用 pass by value 的方式,但因為它複製的是 slice 本身,而這個 slice 卻還是指向在同一個記憶體位址的 Array,這也就是為什麼,在函式中當我們沒有使用 Pointer 的方式去修改這個 slice 時,但它卻還是會改動到 array 內的元素:

Imgur

實際上 slice 並不會保存任何資料,它只是描述底層的 array,當我們修改 slice 中的元素時,實際上是修改底層 array 的元素。不同的 slice 只要共享相同底層的 array 時,就會看到相同對應的變化。

提示

因為帶入 function 的 slice 一樣是 call by value 的緣故,所以要想像複製了一個新的窗戶。

可以參考下面的程式碼:

// Example 1
func main() {
names := []string{
"John",
"Paul",
"George",
"Ringo",
}

a := names[0:2]
b := names[1:3]

fmt.Println(a, b) // [John Paul] [Paul George

b[0] = "XXX" // a 和 b 這兩個 slice 參照到的是底層相同的 array
fmt.Println(a, b) // [John XXX] [XXX George]
fmt.Println(names) // [John XXX George Ringo]
}
// Example 2:當把 slice 傳入 function 中時,雖然仍然是 pass by value,有複製了新的 slice
// 但這個新的 slice 仍然是指稱到底層相同的 array

func main() {
words := []string{"Hello", "every", "one"}
fmt.Println(words) // [Hello every one]
changeSliceItem(words)
fmt.Println(words) // [Hi every one]
}

func changeSliceItem(words []string) {
words[0] = "Hi"
}

slice of pointers or slice of struct

參考 Golang - 使用 slice of pointers 或 slice of structs

Implicit memory aliasing in for loop

Implicit memory aliasing in for loop @ StackOverflow

for i, v := range versions {
// 不要使用 res := createWorkerFor(&v)
res := createWorkerFor(&versions[i]) // 使用 versions[i]
// ...
}

preallocating (prealloc) 的問題

How to resolve Consider preallocating (prealloc) lint? @ stackOverflow

雖然 append 會自動幫我們擴展 slice 的 capacity,但如果可能的話,在建立 slice 時預先定義好 slice 的 length 和 capacity 將可以避免不必要的記憶體分派(memory allocations)。

💡 每次 array 的 capacity 改變時,就會進行一次 memory allocation。

方法一:使用 append ,搭配 length: 0,並設定 capacity 定好:

contacts := make([]model.Contact, 0, len(patientContacts))
for i := range patientContacts {
contact := toInternalContactFromPCC(p, &patientContacts[i], residentID)
contacts = append(contacts, contact)
}

方法二(maybe better):不使用 append,直接設定 lencap 並透過 index 的方式把值給進去:

contacts := make([]model.Contact, len(patientContacts))
for i := range patientContacts {
contact := toInternalContactFromPCC(p, &patientContacts[i], residentID)
contacts[i] = contact
}

奇怪的 slice

範例一

建立一個 toIntPointers 的方法,想要將所有 int 轉成 pointer:

func main() {
intValues := []int{1, 3, 5}
intPointers := toIntPointers(intValues)

fmt.Println(*intPointers[0], *intPointers[1], *intPointers[2]) // 5,5,5
}

使用 intPointers[i] = &v:預期輸出的結果是 1, 3, 5,但得到的是 5, 5, 5

// intPointers[i] = &v 會導致非預期的現象
func toIntPointers(intValues []int) []*int {
intPointers := make([]*int, len(intValues))
for i, v := range intValues {
fmt.Println("i, v", i, v)
intPointers[i] = &v
}

return intPointers
}

使用 intPointers[i] = &intValues[i] 才能得到預期結果:

// intPointers[i] = &v 會導致非預期的現象
func toIntPointers(intValues []int) []*int {
intPointers := make([]*int, len(intValues))
for i, v := range intValues {
fmt.Println("i, v", i, v)
intPointers[i] = &intValues[i]
}

return intPointers
}

之所以會這樣的原因在於,使用 for i, v := range intValues {...} 時,v 是一個變數,在每次迭代時會被賦予不同的值,但實際上參照的是同一個記憶體位置,所以如果使用 intPointers[i] = &v&v 塞到 intPointers 的話,都會塞進指稱到同一個記憶體位址的值,當迴圈跑到最後時這個 v 被賦值為 5,進而導致最終陣列中每一個元素的值都是 5。

golang_slice

參考資料

參考