[譯] Golang 中的數組和切片指南(及其差別)

首先,很容易看到數組和切片好像是同一個東西:表示列表的數據結構。然而,它們實際上彼此徹底不一樣。git

在這篇文章中,咱們將探討他們在 Go 中的差別和實現。github

數組

數組是固定的數據列表。這裏的重點是固定的,由於一旦設置了數組的長度,它就沒法更改。golang

咱們舉一個聲明瞭四個整數的數組的例子:數組

arr := [4]int{3, 2, 5, 4}
複製代碼

長度和類型

咱們在上面的例子中定義的 arr 變量的類型是 [4] int,它是一個大小爲 4 的數組。這裏須要注意的是,4 包含在類型定義中。數據結構

這意味着兩個不一樣長度的數組其實是兩個不一樣的類型。因此不能將不一樣長度的數組視爲一種類型,也不能將其中一個的值分配給另外一個:app

longerArr := [5]int{5, 7, 1, 2, 0}

longerArr = arr
// 會拋出編譯錯誤

longerArr == arr
// 會拋出編譯錯誤
複製代碼

我發現考慮數組的一個好方法就是結構體。若是咱們能夠構造數組等價的結構體,它可能看起來像這樣:ide

// 長度爲 4 的數組的等價結構體
type int4 struct {
  e0 int
  e1 int
  e2 int
  e3 int
}

// 長度爲 5 的數組的等價結構體
type int5 struct {
  e0 int
  e1 int
  e2 int
  e3 int
  e5 int
}

arr := int4{3, 2, 5, 4}
longerArr := int5{5, 7, 1, 2, 0}
複製代碼

不建議執行此操做,但這是一個很好的方法來了解爲什麼不一樣長度的數組是徹底不一樣的類型。函數

內存表示

數組存儲爲指定類型的 n 塊的序列:post

初始化數組類型的變量後,將當即分配此內存。ui

引用傳遞

在 Go 中,沒有引用傳遞。一切都是經過值傳遞的。若是將數組的值分配給另外一個變量,則會複製整個值。

若是隻想將「引用」傳遞給數組,可使用指針:

在內存分配和函數中,數組其實是一種很是簡單的數據類型,其工做方式與結構體相同。

切片

咱們能夠將切片視爲基於數組的高級實現。

在 Go 中實現了切片,以涵蓋開發人員在處理列表時面臨的一些很是常見的需求,例如須要動態修改大小。

聲明切片幾乎與聲明數組相同,除了須要必須省略長度的說明符:

slice := []int{4, 5, 3}
複製代碼

僅僅看代碼的話,切片和數組看起來很是類似,但實際上在實現和使用方面存在顯著差別。

內存表示

切片的分配方式與數組不一樣,其實是修改過的指針。每一個切片包含三條信息:

  1. 指向數據序列的指針
  2. 長度:表示當前包含的元素總數。
  3. 容量:即配置的內存位置總數。

而後,能夠爲彼此的值分配不一樣長度的切片。它們的類型相同,指針,長度和容量都在變化:

slice1 := []int{6, 1, 2}
slice2 := []int{9, 3}

// 能夠將任何長度的切片分配給其餘切片
複製代碼

與數組不一樣,切片在初始化期間不分配數據塊的內存。實際上,切片用 nil 值初始化。

引用傳遞

將切片分配給另外一個變量時,仍然按值傳遞。這裏的值僅指代指針,長度和容量,而不是元素自己佔用的內存。(譯者注:這裏作了一個實驗能夠更清晰地瞭解這個過程)

增長新元素

要向切片添加元素,一般使用 append 函數。

nums := []int{8, 0}
nums = append(nums, 8)
複製代碼

在內部,這會將指定的值分配給新元素,並返回一個新的切片。這個新切片的長度增長了 1。(譯者注:關於切片的擴容分析能夠參考煎魚stefno 的博客)

這就是爲何常常建議建立一個預先指定長度和容量的切片(特別是若是你很清楚它的大小多是多少):

arr := make([]int, 0, 5)
// 這將建立一個長度爲 0 且容量爲 5 的切片
複製代碼

數組和切片的使用場景

數組和切片是徹底不一樣的,所以,它們的用例也是徹底不一樣的。

咱們來看一下開源項目和 Go 標準庫中的一些例子,看看它們怎麼使用的。

例子 1:UUID

UUID 是 128 位數據,一般用來惟一標記對象或實體。一般以短劃線分隔的十六進制值表示:

e39bdaf4-710d-42ea-a29b-58c368b0c53c
複製代碼

Google 的 UUID 庫 中,UUID 表示爲 16 字節的數組:

type UUID [16]byte
複製代碼

這是有意義的,由於咱們知道 UUID 是由 128 位(16 字節)組成的。咱們不會在 UUID 中添加或刪除任何字節,所以使用數組來表示會更好。

例子 2:整數排序

在下一個示例中,咱們將查看排序標準庫中的 sort.Ints 函數:

s := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Ints(s)
fmt.Println(s)
// [1 2 3 4 5 6]
複製代碼

sort.Ints 函數接受一個整數列表並將它們排序。這裏選切片有兩個緣由:

  1. 未指定整數的數量(能夠有任意數量的整數進行排序)。
  2. 這些數字須要原地排序。使用數組會將整個整數列表做爲值傳遞,所以該函數只會對它的副本進行排序,而不是對傳遞給它的值。(譯者注:由於不一樣長度的數組是不一樣的類型,因此 sort.Ints 規定了入參類型爲切片,這也是一個比較重要的緣由)

結論

如今咱們已經介紹了數組和切片之間的關鍵差別及其用例,這裏有一些提示能夠決定哪一種結構更合適:

  1. 若是實體由一組固定長度的非空項組成:使用數組。
  2. 須要對列表進行添加或刪除元素時,請使用切片。
  3. 若是列表能夠包含任意數量的元素,請使用切片。
  4. 你會以某種方式修改列表嗎?若是是,則使用切片

咱們能夠看到,切片涵蓋了在 Go 中建立應用程序的大多數場景。儘管如此,數組確實有它們的位置,而且在須要它們時很是有用。

大家有更好的例子嗎?若是有任何關於你更喜歡切片勝於數組(反之亦然)的例子?請在下面的評論中告訴咱們👇

相關文章
相關標籤/搜索