原文:CalliCoder — Introduction to Slices in Golang
Slice 是陣列的一部分。Slice 建構在陣列之上,與陣列相比提供了更多功能、靈活性和便利性。
如同陣列一樣,Slice 是可索引的且具有長度。但與陣列不同的是,它們可以調整長度。
在內部,Slice 只是對底層陣列的參考。在本文中,我們將學到如何建立和使用 Slice,並了解它們背後的運作方式。
宣告 Slice
使用 []T
宣告型別為 T
的 Slice。舉例來說,以下是宣告 int
型別的 Slice 的方法:
// Slice of type `int`
var s []int
Slice 的宣告就像陣列一樣,只不過我們在中括號 []
中未指定任何長度。
建立和初始化 Slice
1. 使用 Slice 定數建立 Slice
你可以像這樣使用 Slice 定數來建立 Slice:
// Creating a slice using a slice literal
var s = []int{3, 5, 7, 9, 11, 13, 17}
上述陳述式右側的表達式是 Slice 定數。Slice 定數的宣告與陣列定數一樣,不同之處在於沒有在中括號 []
中指定任何長度。
當你使用 Slice 定數建立 Slice 時,它會先建立一個陣列,然後返回參考它的 Slice。
讓我們看一個完整範例:
package main
import "fmt"
func main() {
// Creating a slice using a slice literal
var s = []int{3, 5, 7, 9, 11, 13, 17}
// Short hand declaration
t := []int{2, 4, 8, 16, 32, 64}
fmt.Println("s = ", s)
fmt.Println("t = ", t)
}
# Output
s = [3 5 7 9 11 13 17]
t = [2 4 8 16 32 64]
2. 從陣列建立 Slice
由於 Slice 是陣列的一部分,因此我們可以從陣列建立 Slice。
要從陣列 a
建立 Slice,我們指定兩個由冒號分隔的索引 low
(下限)和 high
(上限):
// Obtaining a slice from an array `a`
a[low:high]
上面的表達式從陣列 a
選擇一個 Slice。Slice 的結果包含從索引 low
到 high
的所有元素,但不包括索引 high
的元素。
讓我們來看一個範例,讓事情更清晰明瞭:
package main
import "fmt"
func main() {
var a = [5]string{"Alpha", "Beta", "Gamma", "Delta", "Epsilon"}
// Creating a slice from the array
var s []string = a[1:4]
fmt.Println("Array a = ", a)
fmt.Println("Slice s = ", s)
}
Array a = [Alpha Beta Gamma Delta Epsilon]
Slice s = [Beta Gamma Delta]
Slice 表達式中的 low
和 high
索引是非必要的。low
的預設值為 0
,而 high
的預設值為 Slice 的長度。
package main
import "fmt"
func main() {
a := [5]string{"C", "C++", "Java", "Python", "Go"}
slice1 := a[1:4]
slice2 := a[:3]
slice3 := a[2:]
slice4 := a[:]
fmt.Println("Array a = ", a)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
fmt.Println("slice3 = ", slice3)
fmt.Println("slice4 = ", slice4)
}
# Output
Array a = [C C++ Java Python Go]
slice1 = [C++ Java Python]
slice2 = [C C++ Java]
slice3 = [Java Python Go]
slice4 = [C C++ Java Python Go]
3. 從一個 Slice 建立另一個 Slice
也可以透過劃分現有的 Slice 來建立 Slice。
package main
import "fmt"
func main() {
cities := []string{"New York", "London", "Chicago", "Beijing", "Delhi", "Mumbai", "Bangalore", "Hyderabad", "Hong Kong"}
asianCities := cities[3:]
indianCities := asianCities[1:5]
fmt.Println("cities = ", cities)
fmt.Println("asianCities = ", asianCities)
fmt.Println("indianCities = ", indianCities)
}
# Output
cities = [New York London Chicago Beijing Delhi Mumbai Bangalore Hyderabad Hong Kong]
asianCities = [Beijing Delhi Mumbai Bangalore Hyderabad Hong Kong]
indianCities = [Delhi Mumbai Bangalore Hyderabad]
修改 Slice
Slice 是參考型別。它們參考到底層陣列。修改 Slice 的元素將會修改參考陣列中的相應元素。引用相同陣列的其他 Slice 也會看到這些修改。
package main
import "fmt"
func main() {
a := [7]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
slice1 := a[1:]
slice2 := a[3:]
fmt.Println("------- Before Modifications -------")
fmt.Println("a = ", a)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
slice1[0] = "TUE"
slice1[1] = "WED"
slice1[2] = "THU"
slice2[1] = "FRIDAY"
fmt.Println("\n-------- After Modifications --------")
fmt.Println("a = ", a)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
}
# Output
------- Before Modifications -------
a = [Mon Tue Wed Thu Fri Sat Sun]
slice1 = [Tue Wed Thu Fri Sat Sun]
slice2 = [Thu Fri Sat Sun]
-------- After Modifications --------
a = [Mon TUE WED THU FRIDAY Sat Sun]
slice1 = [TUE WED THU FRIDAY Sat Sun]
slice2 = [THU FRIDAY Sat Sun]
Slice 的長度和容量
Slice 由三樣東西組成:
指向底層陣列的指標(參考)。
Slice 包含的陣列部分的長度。
容量(該部分可以增加到的最大大小)
讓我們用以下陣列和從中得到的 Slice 作為範例:
var a = [6]int{10, 20, 30, 40, 50, 60}
var s = [1:4]
這是上面範例中的 Slice s
的表示方式:
Slice 的長度是 Slice 中元素的數量,在上面的範例中為 3
。
容量是從 Slice 中第一個元素開始的底層陣列中的元素數量,在上面的範例中為 5
。
你可以使用內建函數 len()
和 cap()
找到 Slice 的長度和容量:
package main
import "fmt"
func main() {
a := [6]int{10, 20, 30, 40, 50, 60}
s := a[1:4]
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [20 30 40], len = 3, cap = 5
透過重新劃分,可以將 Slice 的長度擴充到其容量。任何嘗試將其長度擴充到可用容量之外的行為將導致執行時期錯誤。
查看以下範例來了解如何重新劃分指定 Slice 來更改其長度和容量:
package main
import "fmt"
func main() {
s := []int{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}
fmt.Println("Original Slice")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
s = s[1:5]
fmt.Println("\nAfter slicing from index 1 to 5")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
s = s[:8]
fmt.Println("\nAfter extending the length")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
s = s[2:]
fmt.Println("\nAfter dropping the first two elements")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
Original Slice
s = [10 20 30 40 50 60 70 80 90 100], len = 10, cap = 10
After slicing from index 1 to 5
s = [20 30 40 50], len = 4, cap = 9
After extending the length
s = [20 30 40 50 60 70 80 90], len = 8, cap = 9
After dropping the first two elements
s = [40 50 60 70 80 90], len = 6, cap = 7
使用內建 make()
函數建立 Slice
現在我們知道了 Slice 的長度和容量。讓我們來看看建立 Slice 的另一種方法。
Golang 提供了一個名為 make()
的函數來建立 Slice。以下是 make()
函數的簽名:
func make([]T, len, cap) []T
make 函數需要型別、長度和容量(非必要)。它分配了長度與給定容量相同的底層陣列,並返回參考該陣列的 Slice。
package main
import "fmt"
func main() {
// Creates an array of size 10, slices it till index 5, and returns the slice reference
s := make([]int, 5, 10)
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [0 0 0 0 0], len = 5, cap = 10
make()
函數中的容量參數是非必要的。如果省略,則預設為指定的長度:
package main
import "fmt"
func main() {
// Creates an array of size 5, and returns a slice reference to it
s := make([]int, 5)
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [0 0 0 0 0], len = 5, cap = 5
Slice 的零值
Slice 的零值是 nil
。具有零值的 Slice 沒有任何底層陣列,且長度和容量為 0
:
package main
import "fmt"
func main() {
var s []int
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
if s == nil {
fmt.Println("s is nil")
}
}
# Output
s = [], len = 0, cap = 0
s is nil
Slice 函數
1. copy() 函數:複製 Slice
copy()
函數將元素從一個 Slice 複製到另一個 Slice。它的簽名看起來像樣這樣:
func copy(dst, src []T) int
它需要兩個 Slice:來源 Slice 和目的 Slice。然後它會將元素從來源複製到目的,並返回複製的元素數量。
複製的元素數量將是 len(src)
和 len(dst)
的最小值。
package main
import "fmt"
func main() {
src := []string{"Sublime", "VSCode", "IntelliJ", "Eclipse"}
dest := make([]string, 2)
numElementsCopied := copy(dest, src)
fmt.Println("src = ", src)
fmt.Println("dest = ", dest)
fmt.Println("Number of elements copied from src to dest = ", numElementsCopied)
}
# Output
src = [Sublime VSCode IntelliJ Eclipse]
dest = [Sublime VSCode]
Number of elements copied from src to dest = 2
2. append() 函數:附加到 Slice
append()
函數在給定的 Slice 結尾附加新元素。以下是 append
函數的簽名。
func append(s []T, x ...T) []T
它需要一個 Slice 和可變數量的參數 x …T。然後,它返回一個新的 Slice ,其中包含給定 Slice 中的所有元素及新元素。
如果給定的 Slice 沒有足夠的容量來容納新元素,則將分配具有更大容量的新底層陣列。現有 Slice 的底層陣列中的所有元素都將複製到新陣列,然後附加新元素。
但是,如果 Slice 具有足夠的容量來容納新元素,則 append()
函數將再使用其底層陣列並將新元素附加到同一陣列中。
讓我們來看一個範例,以更好地理解:
package main
import "fmt"
func main() {
slice1 := []string{"C", "C++", "Java"}
slice2 := append(slice1, "Python", "Ruby", "Go")
fmt.Printf("slice1 = %v, len = %d, cap = %d\n", slice1, len(slice1), cap(slice1))
fmt.Printf("slice2 = %v, len = %d, cap = %d\n", slice2, len(slice2), cap(slice2))
slice1[0] = "C#"
fmt.Println("\nslice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
}
# Output
slice1 = [C C++ Java], len = 3, cap = 3
slice2 = [C C++ Java Python Ruby Go], len = 6, cap = 6
slice1 = [C# C++ Java]
slice2 = [C C++ Java Python Ruby Go]
在上面的範例中,由於 slice1
的容量為 3,它無法容納更多的元素。因此,當我們對它附加更多元素時,新的底層陣列分配了更大的容量。
所以,如果你修改 slice1
,slice2
將不會看到這些更改,因為它參考了另一個陣列。
但是,如果 slice1
具有足夠的容量來容納新元素呢? 嗯,在那種情況下,將不會分配新的陣列,並且會將元素附加到同一底層陣列中。
同樣地,在這種情況下,對 slice1
的更改也會影響 slice2
,因為兩者都參考相同的底層陣列。
在以下範例中對此進行了示範:
package main
import "fmt"
func main() {
slice1 := make([]string, 3, 10)
copy(slice1, []string{"C", "C++", "Java"})
slice2 := append(slice1, "Python", "Ruby", "Go")
fmt.Printf("slice1 = %v, len = %d, cap = %d\n", slice1, len(slice1), cap(slice1))
fmt.Printf("slice2 = %v, len = %d, cap = %d\n", slice2, len(slice2), cap(slice2))
slice1[0] = "C#"
fmt.Println("\nslice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
}
# Output
slice1 = [C C++ Java], len = 3, cap = 10
slice2 = [C C++ Java Python Ruby Go], len = 6, cap = 10
slice1 = [C# C++ Java]
slice2 = [C# C++ Java Python Ruby Go]
附加到具有零值的 Slice
當你將值附加到 nil
的 Slice,它分配一個新的 Slice,並返回新 Slice 的參考。
package main
import "fmt"
func main() {
var s []string
// Appending to a nil slice
s = append(s, "Cat", "Dog", "Lion", "Tiger")
fmt.Printf("s = %v, len = %d, cap = %d\n", s, len(s), cap(s))
}
# Output
s = [Cat Dog Lion Tiger], len = 4, cap = 4
將一個 Slice 附加到另一個 Slice
你可以使用 ...
運算子將一個 Slice 直接附加到另一個 Slice。該運算元將 Slice 展開為參數列表。以下的範例示範了其用法:
package main
import "fmt"
func main() {
slice1 := []string{"Jack", "John", "Peter"}
slice2 := []string{"Bill", "Mark", "Steve"}
slice3 := append(slice1, slice2...)
fmt.Println("slice1 = ", slice1)
fmt.Println("slice2 = ", slice2)
fmt.Println("After appending slice1 & slice2 = ", slice3)
}
# Output
slice1 = [Jack John Peter]
slice2 = [Bill Mark Steve]
After appending slice1 & slice2 = [Jack John Peter Bill Mark Steve]
Slice 中的 Slice
Slice 可以是任何型別。它們還可以包含其他 Slice。以下範例建立了 Slice 中的 Slice:
package main
import "fmt"
func main() {
s := [][]string{
{"India", "China"},
{"USA", "Canada"},
{"Switzerland", "Germany"},
}
fmt.Println("Slice s = ", s)
fmt.Println("length = ", len(s))
fmt.Println("capacity = ", cap(s))
}
# Output
Slice s = [[India China] [USA Canada] [Switzerland Germany]]
length = 3
capacity = 3
迭代 Slice
你可以用與迭代陣列相同的方式迭代 Slice。以下是迭代 Slice 的兩種方法:
1. 使用 for
迴圈迭代 Slice
package main
import "fmt"
func main() {
countries := []string{"India", "America", "Russia", "England"}
for i := 0; i < len(countries); i++ {
fmt.Println(countries[i])
}
}
# Output
India
America
Russia
England
2. 使用 range
形式的 for
迴圈迭代 Slice
package main
import "fmt"
func main() {
primeNumbers := []int{2, 3, 5, 7, 11, 13, 17, 19, 23, 29}
for index, number := range primeNumbers {
fmt.Printf("PrimeNumber(%d) = %d\n", index+1, number)
}
}
# Output
PrimeNumber(1) = 2
PrimeNumber(2) = 3
PrimeNumber(3) = 5
PrimeNumber(4) = 7
PrimeNumber(5) = 11
PrimeNumber(6) = 13
PrimeNumber(7) = 17
PrimeNumber(8) = 19
PrimeNumber(9) = 23
PrimeNumber(10) = 29
使用空白識別符號從 range
形式的 for
迴圈中忽略 index
range
形式的 for
迴圈在每次迭代中為你提供 index
和該索引處的 value
。如果你不想使用 index
,可以使用底線 _
將其丟棄。
底線(_
)稱為空白識別字元。它用於告訴編譯器我們不需要此數值。
package main
import "fmt"
func main() {
numbers := []float64{3.5, 7.4, 9.2, 5.4}
sum := 0.0
for _, number := range numbers {
sum += number
}
fmt.Printf("Total Sum = %.2f\n", sum)
}
# Output
Total Sum = 25.50
結論
在本文中,你學到了如何建立 Slice、Slice 內部如何運作,以及如何使用內建函數 copy()
和 append()
來增加 Slice。
在下一篇文章中,我們將學到另一個非常有用的資料結構:map。