Published on

[Go 學習系列] 7. Slice 與 Map 進階操作與實戰

Authors
  • avatar
    Name
    Vic Chen
    Twitter

前言

上一章我們學會了 Slice 與 Map 的基本操作,
但實務上常遇到一些進階情況,例如:

  • Slice 底層陣列共用導致修改影響其他 slice
  • Map 在 range 中修改元素要小心
  • Slice append 的容量擴充機制

這篇我們就來深入這些進階議題,並做一些實戰練習。


一、Slice 底層共用行為

Slice 是對底層陣列的引用,所以多個 slice 可能共用同一底層。

a := []int{1, 2, 3, 4, 5}
b := a[1:4] // [2 3 4]

b[0] = 99
fmt.Println(a) // [1 99 3 4 5]
fmt.Println(b) // [99 3 4]

NOTE

修改 slice 的元素會影響原始底層陣列。


避免底層共用的方法

使用 copy() 建立獨立 slice:

a := []int{1, 2, 3, 4, 5}
b := make([]int, 3)
copy(b, a[1:4])
b[0] = 99

fmt.Println(a) // [1 2 3 4 5]
fmt.Println(b) // [99 3 4]

二、Slice append 與容量擴充

Slice 的容量(cap)決定底層陣列大小。當 append 超過容量時,Go 會自動建立新的底層陣列。

a := make([]int, 2, 3)
fmt.Println(len(a), cap(a)) // 2 3

a = append(a, 10)
fmt.Println(len(a), cap(a)) // 3 3

a = append(a, 20)
fmt.Println(len(a), cap(a)) // 4 6 (容量自動擴充)

TIP

容量倍增策略可以減少多次 reallocation,提高效能。


三、Map 迭代與修改

1️⃣ range 注意事項

在迭代 Map 時,如果直接修改元素:

m := map[string]int{"a": 1, "b": 2}

for k, v := range m {
    m[k] = v * 2
}
fmt.Println(m) // OK

可以修改值,但不能直接修改 key。Map 本身是無序的,每次迭代順序可能不同。


2️⃣ 刪除元素

在迴圈中刪除元素是安全的:

for k := range m {
    if k == "a" {
        delete(m, k)
    }
}
fmt.Println(m)

四、Slice 與 Map 實戰範例

1️⃣ 去重(slice 去除重複元素)

func unique(nums []int) []int {
    seen := make(map[int]bool)
    result := []int{}

    for _, n := range nums {
        if !seen[n] {
            result = append(result, n)
            seen[n] = true
        }
    }

    return result
}

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

2️⃣ 統計字串出現頻率

func countWords(words []string) map[string]int {
    counter := make(map[string]int)
    for _, w := range words {
        counter[w]++
    }
    return counter
}

func main() {
    fruits := []string{"apple", "banana", "apple", "orange", "banana"}
    fmt.Println(countWords(fruits))
}

3️⃣ 篩選 Slice

func filterEven(nums []int) []int {
    result := []int{}
    for _, n := range nums {
        if n%2 == 0 {
            result = append(result, n)
        }
    }
    return result
}

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

五、效能與注意事項

  • Slice append 時盡量預估容量,避免頻繁擴充
  • Map 迭代順序不固定,若有順序需求需額外排序
  • 多 goroutine 修改 map 或 slice 必須加鎖 (sync.Mutex)
  • 避免直接共享 slice 引用造成意外修改

結語

這篇我們掌握了:

  • Slice 的底層共用與 copy 技巧
  • Slice append 與容量擴充策略
  • Map 的迭代、修改與刪除
  • 實戰範例:去重、統計、篩選

下一篇,我們將進入 Golang 並行程式設計入門:Goroutine
正式開始探索 Go 的高效能並行能力 🚀