👨‍💻簡介

在 Go 語言中,切片(Slice)是一種動態序列的資料結構,能夠方便地存儲和操作多個相同類型的元素。切片相比於陣列,更具有彈性,因為它的大小是可變的,可以根據需要動態增長或縮小。切片在處理集合型資料時非常實用,讓你能夠輕鬆地新增、刪除、修改和操作元素,同時避免了固定大小的限制。

切片的基本概念

切片是由一個指向陣列的指針、切片長度和切片容量組成的。切片的長度指的是切片中實際包含的元素個數,而切片的容量則是從切片開始的底層陣列中能夠訪問的元素個數。切片的容量可以大於或等於切片的長度。

宣告和初始化切片

在 Go 中,宣告和初始化切片可以使用以下的語法:

var slice1 []int                     // 創建一個整數切片,大小和容量都是 0
slice2 := []string{"A", "B", "C"}    // 創建一個包含三個字串元素的切片
slice3 := make([]int, 5)             // 創建一個整數切片,大小為 5,容量也為 5
slice4 := make([]int, 3, 5)          // 創建一個整數切片,大小為 3,容量為 5

切片的基本操作

新增元素到切片

Go語言中有個內件函式append(),可以用來新增元素到切片中,這使得切片能夠根據需求動態增長。

numbers := []int{10, 20, 30}
numbers = append(numbers, 40, 50) // 新增兩個元素到切片

需要注意的是,如果切片的容量不足以容納新增的元素,append() 函式會創建一個新的切片,並將舊的元素和新的元素一同複製到新的切片中。

查詢和修改切片元素

切片的元素可以通過索引來存取,索引從 0 開始計數。

numbers := []int{10, 20, 30, 40, 50}
firstNumber := numbers[0] // 存取第一個元素,值為 10
secondNumber := numbers[1] // 存取第二個元素,值為 20

numbers[2] = 35 // 修改切片中的元素,將第三個元素改為 35

切割和遍歷切片

可以使用切片的切割操作來獲取部分切片,這可以通過指定切片的起始索引和結束索引來實現:

numbers := []int{10, 20, 30, 40, 50}
subSlice := numbers[1:4] // 從第二個元素到第四個元素的子切片,值為 [20 30 40]
  • [:]:使用 [:] 語法可以獲得完整的切片,即包含所有元素的切片。
  • [x:]:這種語法表示從索引 x 開始,提取所有直到切片結尾的元素。
  • [:x]:這種語法表示提取從開頭到索引 x-1 的元素,不包含索引 x 的元素。

切片的遍歷可以使用傳統的索引方式,或者使用 range 來簡化遍歷過程。

numbers := []int{10, 20, 30, 40, 50}

// 使用傳統的索引方式遍歷切片
for i := 0; i < len(numbers); i++ {
    fmt.Println(numbers[i])
}

// 使用 range 遍歷切片
for index, value := range numbers {
    fmt.Printf("索引:%d,值:%d\n", index, value)
}

for i := range numbers {
    fmt.Println(numbers[i])
}

刪除切片元素

雖然切片沒有內建的刪除方法,不過可以使用切片的切割操作,將不需要的元素排除在外。

slice := []int{10, 20, 30, 40, 50}
indexToDelete := 2
slice = append(slice[:indexToDelete], slice[indexToDelete+1:]...) // 刪除索引 2 的元素

切片的容量

切片的容量就是指,你從切片開始的位置能夠往後訪問到的元素數量。當你在切片上進行新增元素的操作時,如果新加進來的元素數量超過了切片目前的容量,Go 語言會做一些事情:它會重新搞一個更大的陣列,然後把舊的元素都複製到這個新陣列中。這個過程會花一些時間,也就是所謂的性能開銷。

package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3}
	fmt.Println("長度:", len(numbers)) // 輸出:長度:3
	fmt.Println("容量:", cap(numbers)) // 輸出:容量:3

	slice := numbers[1:2]
	fmt.Println("切片:", slice)        // 輸出:切片:[2]
	fmt.Println("切片長度:", len(slice)) // 輸出:切片長度:1
	fmt.Println("切片容量:", cap(slice)) // 輸出:切片容量:2
	// 切片的容量是從切片的起始索引開始計算,一直到底層陣列的末尾索引。因此容量為2

	// 新增元素超過切片容量時,會自動擴容並分配更大的底層陣列
	slice = append(numbers, 4)       // 新切片為 [1, 2, 3, 4]
	fmt.Println("切片:", slice)        // 輸出:切片:[1 2 3 4]
	fmt.Println("切片長度:", len(slice)) // 輸出:切片長度:4
	fmt.Println("切片容量:", cap(slice)) // 輸出:切片容量:6
}

切片的特性、限制與使用場景

切片的特性

  • 切片的大小是動態的,可以隨時進行增長或縮小。
  • 切片可以方便地從現有的陣列或切片中創建,不需要顯式地指定容量。
  • 切片的新增、刪除、修改操作都非常方便,讓你能夠輕鬆地操作集合型資料。
  • 切片的背後通常使用動態陣列來實現,因此在內存佈局上更具彈性,不受固定大小的限制。
  • 切片是引用類型,這意味著它們引用底層的陣列,而不是複製數據。當你將一個切片賦值給另一個切片,它們實際上共享同一份數據。因此,對其中一個切片所做的修改將影響到另一個切片。 以下是一個實際案例:
slice1 := []int{1, 2, 3}
slice2 := slice1
slice2[0] = 3
fmt.Println(slice1, slice2)  // 輸出:[3 2 3] [3 2 3]

在這裡,修改 slice2 的值同樣影響了 slice1,因為它們引用同一份底層數據。這是切片和陣列之間的一個重要區別。

切片的限制

  • 記憶體配置: 切片的內部實現中使用了底層的陣列,因此切片所佔用的記憶體空間可能比實際所需大。當切片的元素數量增加時,Go 語言可能會重新配置更大的陣列,導致一些性能開銷。
  • 不適用於大型資料: 對於大型資料集合,切片可能不是最佳選擇。大型切片可能導致記憶體占用過多,影響程式的效能。
  • 不支援多維切片: 切片僅支援一維結構,如果需要處理多維資料,可能需要自行實現多維切片的概念。

使用場景

  • 動態資料集合: 切片的大小是可變的,使其非常適用於處理動態資料集合,無需事先知道集合的大小。
  • API 請求和回應: 在處理 API 請求和回應時,切片可用於儲存和解析不確定數量的請求參數或回應資料。
  • 資料過濾和轉換: 使用 range 遍歷切片,可以輕鬆地過濾或轉換切片中的元素,進行處理和計算。
  • 傳遞函式參數: 切片的引用類型特性使其適用於傳遞函式參數,避免不必要的資料複製。
  • 檔案讀寫: 切片可用於讀取大量數據,進行處理後再寫回檔案。
  • 實現堆疊和佇列: 使用切片可以實現簡單的堆疊(後進先出)和佇列(先進先出)結構。
  • 排序和搜尋: 切片在排序和搜尋資料時非常有用,可以利用 sort 套件進行排序,並使用二分搜尋等技術進行搜尋。

常見用法

  • 資料過濾和轉換
// 將切片中的所有元素乘以 2
numbers := []int{1, 2, 3, 4, 5}
for i := range numbers {
    numbers[i] *= 2
}

// 過濾出大於 3 的元素
filteredNumbers := []int{}
for _, num := range numbers {
    if num > 3 {
        filteredNumbers = append(filteredNumbers, num)
    }
}
  • 傳遞函式參數
func modifySlice(slice []int) {
    slice[0] = 100
}

numbers := []int{1, 2, 3}
modifySlice(numbers)

小結

切片是 Go 語言中非常實用的資料結構,它解決了陣列大小固定的限制,讓你能夠輕鬆地處理動態大小的集合型資料。切片的操作包括取得、修改、新增、刪除等,讓你可以靈活地處理資料集合。

在遍歷切片時,range 很方便地提供了索引和值,讓你能夠輕鬆地訪問切片中的元素。切片的特性和優勢使得它在處理動態數據集合時成為 Go 程式設計中的一個重要工具。

📚Reference