iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
Modern Web

Go 快 Go 高效: 從基礎語法到現代Web應用開發系列 第 8

【Day08】Golang 基礎語法 | 陣列與切片(Arrays & Slices)

  • 分享至 

  • xImage
  •  

在 Golang 中,陣列 Arrays 與切片 Slices 是最基本的資料結構之一。兩者有相似的地方,但切片比陣列更加靈活且高效,通常在開發中更推薦使用切片。本文將介紹陣列與切片的基礎語法,並探討切片的容量管理與應用場景。

陣列(Arrays)

在 Golang 中,陣列是固定大小的資料集合,一旦宣告,大小就無法改變。陣列中的每個元素都有一個固定的記憶體位置

  • 範例:
var arr [5]int // 宣告一個大小為 5 的整數陣列
arr[0] = 10    // 為陣列的第一個元素賦值
fmt.Println(arr)

這段程式碼建立了一個大小為 5 的整數陣列 arr,並將第一個元素設為 10。

  • 記憶體位置:
import (
    "fmt"
)

func main() {
    arr := [3]int{1, 2, 3}
    fmt.Printf("arr[0] 的位址: %p\n", &arr[0])
    fmt.Printf("arr[1] 的位址: %p\n", &arr[1])
    fmt.Printf("arr[2] 的位址: %p\n", &arr[2])
}
</* Output: */> 
arr[0] 的位址: 0x140000160c0
arr[1] 的位址: 0x140000160c8
arr[2] 的位址: 0x140000160d0
  • 輸出結果會顯示每個陣列元素的記憶體地址,這說明陣列元素是連續存儲的。

(小提示:如果自行實作時,顯示的位址可能會和我上面的例子有所不同,屬於正常現象。)


切片(Slices)

切片與陣列不同,切片的大小是動態的,可以根據需要增長或縮減。切片是 Golang 中更推薦使用的資料結構,因為它更靈活且有效率。

  • 範例:
nums := []int{1, 2, 3, 4, 5} // 宣告一個切片
fmt.Println(nums)
  • 使用 make() 建立切片:

make() 是 Golang 中用來建立切片的函數,我們可以指定切片的長度和容量。

nums := make([]int, 3, 5) // 建立一個長度為 3,容量為 5 的切片
nums[0] = 10
fmt.Println(nums)         // 輸出:[10 0 0]

這裡的切片 nums 長度是 3,表示有 3 個元素可供使用,容量為 5,表示它最多可以容納 5 個元素而不需要重新分配記憶體。

使用切片而非陣列

  • 為什麼推薦使用切片?

    • 動態增長:切片可以根據需要動態增加大小,而陣列一旦定義,其大小無法改變。
    • 資源效率:切片基於陣列實現,但它只是一個指向底層陣列的引用,這樣能夠更靈活地管理資源,而不需要每次都複製整個陣列。

為什麼需要 make()

在 Golang 中,make() 函數是一個用來初始化特定類型的內建函數,主要用於創建切片(slices)字典(maps)通道(channels)。這三種資料結構是基於引用的資料結構,無法像其他類型一樣通過簡單的變量宣告來使用,因此需要通過 make() 來進行初始化。

  • make():專門用於創建切片、字典和通道,並且進行初始化,返回具備完整操作能力的資料結構。

  • new():用來為任何類型分配內存,返回該類型的指針,但不進行初始化。new() 返回的指針需要手動初始化才能使用。(指針的使用會在第12天做更為詳細的介紹!)

  • 比較:

p := new([]int)  // 返回的是切片的指針,需要初始化才能使用
*p = make([]int, 5) // 這裡才進行了初始化
fmt.Println(*p)     // 輸出:[0 0 0 0 0]

q := make([]int, 5) // 直接使用 make() 創建並初始化
fmt.Println(q)      // 輸出:[0 0 0 0 0]

容量管理

當切片的大小超過其容量時,Golang 會自動分配一個新的、容量更大的底層陣列,並將舊切片的數據複製到新陣列中。這個過程可能導致性能問題,特別是當處理大量數據時。

  • 控制切片的容量
nums := make([]int, 3, 5) // 建立長度為 3、容量為 5 的切片
fmt.Printf("長度: %d, 容量: %d\n", len(nums), cap(nums))

nums = append(nums, 4, 5, 6) // 添加三個元素,超過原始容量
fmt.Printf("長度: %d, 容量: %d\n", len(nums), cap(nums)) // 新容量會擴增
</* Output: */>
長度: 3, 容量: 5
長度: 6, 容量: 10

這裡展示了當切片超過其初始容量時,Golang 會自動擴展切片的容量。

內存複製

內存複製是指將資料從一個記憶體位置複製到另一個記憶體位置的過程。在程式執行過程中,內存複製通常發生在你需要將資料從一個地方複製到另一個地方時,例如擴展陣列或切片時的操作。

像是上面的例子,由於原來的容量不足以容納新元素,Golang 會自動分配一塊新的、更大的記憶體區域(通常是原來容量的兩倍),然後將原來的數據 1, 2 複製到新的區域,再把新元素 3 加進去。這個過程就是內存複製

每次執行內存複製都會消耗 CPU 和記憶體資源,特別是在處理大量資料時,頻繁的內存複製會導致程式性能下降。


避免不必要的內存複製

在處理大量數據時,應避免頻繁的內存重分配。可以通過預先分配足夠的容量來減少內存複製。

nums := make([]int, 0, 1000) // 預先分配容量 1000
for i := 0; i < 1000; i++ {
    nums = append(nums, i)
}
fmt.Println("容量:", cap(nums)) // 確保沒有多餘的內存分配

這樣可以確保在迴圈中不會過度擴展切片,減少內存分配的開銷。


總結

今天我們學到了 Golang 中 陣列與切片 的基礎語法。陣列是固定大小的,而切片則更靈活,能夠動態增長。切片是更推薦的資料結構,並且可以透過 make() 來管理切片的長度與容量。我們也探討了如何避免頻繁的內存複製,以提升程式效能,並強調了在處理大量數據時,預先分配足夠的容量是非常重要的。


上一篇
【Day07】Golang 基礎語法 | 迴圈與迭代(For Loop)
下一篇
【Day09】封裝與多態性 I | Struct & iota & Interface
系列文
Go 快 Go 高效: 從基礎語法到現代Web應用開發21
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言