Calvert's murmur

Golang Slice 介紹

2019-11-13

約 9894 字 / 需 54 分鐘閱讀

原文:CalliCoderIntroduction 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 的結果包含從索引 lowhigh 的所有元素,但不包括索引 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 表達式中的 lowhigh 索引是非必要的。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,它無法容納更多的元素。因此,當我們對它附加更多元素時,新的底層陣列分配了更大的容量。

所以,如果你修改 slice1slice2 將不會看到這些更改,因為它參考了另一個陣列。

但是,如果 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。

Tags: Golang