昨天學習在 Go 語言中的陣列 (Array) ,但是因為陣列需要在定義陣列時就要賦予陣列長度,若是今天陣列長度有變化,那就需要重新定義一個新的陣列,這樣嚴格的限制總會產生些不便,於是解決陣列問題的 切片(slice) 便出現了!
切片的核心是陣列,也就是在陣列外面再多包一層,不僅可以保有本來陣列有數字索引以及有序集合的優點外,更解決了陣列長度嚴格規定的問題,也就是說關於長度問題 Go 語言會自己動態調整 (看著辦處理啦) ~
切片跟陣列一樣只能容納 單一型別
的元素,也可以用 [索引值]
讀取或是更改其中元素,當然也可以用 迴圈
來走訪切片。
其中特別的是切片可以用 Go 語言的內建函式 append()
來新增切片內的元素,且會返回一個 加入新元素的新的切片 。
<新切片> = append(<切片>, <新元素>)
補充:
大部分時候會先建立一個空切片,然後當有資料時,再將資料加入到切片中。雖然切片是以陣列為核心來建立的,但大多時候我們還是會首先以切片來處理有序的集合,因為切片的使用比陣列更加有彈性,不用擔心今天使用陣列元素長度突然有變化時需要重新宣告一個陣列。
這邊範例我們使用 os 套件的 Args 變數, os.Args
是當使用者用 go run 執行程式時,可以在後方附加數量不定的字串參數,是一個字串切片型別。補充:
os.Args 的第一個參數是執行的程式名稱,第二個參數起才是真的參數。
範例 1:
package main
import (
"fmt"
"os"
)
// 使用 os.Args 將我們在 go run 後面輸入的參數,加到新切片中
func getArgs() []string {
if len(os.Args) < 1 {
fmt.Println("您沒有輸入參數,至少需傳入1個參數")
os.Exit(1) // 若沒有傳入參數就強制結束程式
}
var argsSlice []string // 建立一個名為 argsSlice 的空切片
for i := 1; i < len(os.Args); i++ { // 因為 os.Args 的第一個參數是執行的程式名稱,所以迴圈從索引 1 開始走訪
argsSlice = append(argsSlice,os.Args[i]) // 將我們在 go run 後面輸入的參數,使用 os.Args 一一使用 append() 加到新切片中
}
return argsSlice // 回傳新切片
}
// 將傳入的切片內的元素一一遍歷,找出長度最長的字串
func findLongestString(argsSlice []string) string {
var longestString string // 建立一個字串型別的空字串
for i := 0; i < len(argsSlice); i++ {
if len(argsSlice[i]) > len(longestString){ // 如果argsSlice 索引值內的字串長度 > longestString 這個字串
longestString = argsSlice[i] // 將 longestString 的值改為 argsSlice[i] 的值
}
}
return longestString // 回傳長度最長的字串
}
func main() {
fmt.Println(getArgs()) // 印出新切片
fmt.Println(findLongestString(getArgs())) // 找出切片內長度最長的字串元素
}
範例 1(執行結果):
➜ Gogo go run . The mango is my favorite fruit // Gogo 是我的檔案名稱; The mango is my favorite fruit 是我輸入得參數
[The mango is my favorite fruit]
favorite
範例 1 我們使用迴圈,將值一一加到陣列中,其實 append() 也可以一次加多個值到陣列中,因為 append() 函式的第二個參數是可以接受 數量不定的值 ,當然你也可以直接傳入一個切片,在切片後方加上 ...(解包算符 unpack operator ) 來解開切片將值一一傳入,如此一來其實並不需要使用迴圈,可以精簡程式碼。
<新切片> = append(<切片>, <新元素 1>, <新元素 2>, <新元素 3>, ...,<新元素 n>)
範例 2:
package main
import (
"fmt"
"os"
)
// 使用 os.Args 將我們在 go run 後面輸入的參數,加到新切片中
func getArgs() []string {
if len(os.Args) < 1 {
fmt.Println("您沒有輸入參數,至少需傳入1個參數")
os.Exit(1) // 強制結束程式
}
var argsSlice []string // 建立一個名為 argsSlice 的空切片
for i := 1; i < len(os.Args); i++ { // 因為 os.Args 的第一個參數是執行的程式名稱,所以迴圈從索引 1 開始走訪
argsSlice = append(argsSlice,os.Args[i]) // 將我們在 go run 後面輸入的參數,使用 os.Args 一一使用 append() 加到新切片中
}
return argsSlice // 回傳新切片
}
// 將傳入的切片內的元素一一遍歷,找出長度最長的字串
func addMultipleArgs(argsSlice []string) []string {
var multipleArgsString []string
multipleArgsString = append(multipleArgsString,argsSlice...) // 在 argsSlice 切片後方加上 ... 來解開切片將值一一傳入
multipleArgsString = append(multipleArgsString,"水蜜桃","哈密瓜") // 再一次傳入多個值到切片中
return multipleArgsString
}
func main() {
fmt.Println(getArgs()) // 印出新切片
fmt.Println(addMultipleArgs(getArgs())) // 找出切片內長度最長的字串元素
}
範例 2(執行結果):
➜ Gogo go run . 我 最愛的 水果 有 芒果
[我 最愛的 水果 有 芒果]
[我 最愛的 水果 有 芒果 水蜜桃 哈密瓜]
有時候我們只想要擷取某個陣列或切片中的某一段製成新的切片,就可以使用此方法,但要特別注意結束所以值是不包含在內的喔!
<新切片> = <陣列或切片>[<起始索引值>:<結束索引值(不包含此值)>]
補充:
起始值或是結束值都可以省略,若省略起始值,會從索引 0 起算; 若省略結束值,會算到最後一個索引值; 當然也可以兩者都省略成 [:] ,那其實就是跟原集合一樣的元素內容。
範例 3:
package main
import "fmt"
func newSlice() string {
m := []int{0,1,2,3,4,5,6,7,8,9}
fmt.Println("原本的陣列或切片:",m)
message := fmt.Sprintln("第一個元素:",m[0],m[0:1],m[:1])
message += fmt.Sprintln("最後一個元素:",m[len(m)-1],m[len(m)-1:len(m)],m[len(m)-1:])
message += fmt.Sprintln("前兩個元素:",m[0:2],m[:2])
message += fmt.Sprintln("倒數兩個元素:",m[len(m)-2:len(m)],m[len(m)-2:])
message += fmt.Sprintln("第 2 到第 6 個元素:",m[2:7])
message += fmt.Sprintln("全部的元素:",m[:])
return message
}
func main() {
fmt.Println(newSlice())
}
範例 3(執行結果):
原本的陣列或切片: [0 1 2 3 4 5 6 7 8 9]
第一個元素: 0 [0] [0]
最後一個元素: 9 [9] [9]
前兩個元素: [0 1] [0 1]
倒數兩個元素: [8 9] [8 9]
第 2 到第 6 個元素: [2 3 4 5 6]
全部的元素: [0 1 2 3 4 5 6 7 8 9]
通過上面的範例,我們看到各種擷取一段成新切片的方式,但是特別注意即使是將本來的陣列或是切片全部元素取出,這並不是複製一個新的切片,而是共用同一個底層的切片喔! 好啊!又開始講我聽不懂的中文了... 這探討到切片的底層運作,那就明天繼續詳細介紹切片的內部運作吧!