首頁 系統教程 Linux 在 Go 中使用切片的容量和長度的技巧

在 Go 中使用切片的容量和長度的技巧

Mar 20, 2024 pm 02:36 PM
linux linux教程 紅帽 linux系統 linux指令 linux認證 紅帽linux linux視頻

在 Go 中使用切片的容量和长度的技巧

#快速測試 - 下面的程式碼輸出什麼?

vals := make([]int, 5)
for i := 0; i < 5; i {
  vals = append(vals, i)
}
fmt.Println(vals)
登入後複製
登入後複製

如果你猜的是 [0 0 0 0 0 0 1 2 3 4],那你是對的。

如果你在測驗中做錯了,你也不用擔心。這是在過渡到 Go 語言的過程中相當常見的錯誤,在這篇文章中,我們將說明為什麼輸出不是你預期的,以及如何利用 Go 的細微差別來使你的程式碼更有效率。

切片 vs 陣列
#

在 Go 中同時有陣列(array)和切片(slice)。這可能令人困惑,但一旦你習慣了,你會喜歡上它。請相信我。

切片和數組之間存在許多差異,但我們要在本文中重點介紹的內容是數組的大小是其類型的一部分,而切片可以具有動態大小,因為它們是圍繞數組的封裝。

這在實踐中意味著什麼?那麼假設我們有數組 val a [10]int。此數組具有固定大小,且無法變更。如果我們呼叫 len(a),它總是會回傳 10,因為這個大小是類型的一部分。因此,如果你突然需要在數組中超過 10 個項,則必須建立一個完全不同類型的新對象,例如 val b [11]int,然後將所有值從 a 複製到 b。

在特定情況下,含有集合大小的陣列是有價值的,但一般而言,這不是開發人員想要的。相反,他們希望在 Go 中使用類似於數組的東西,但是隨著時間的推移,它們能夠隨時增長。一個粗略的方式是建立一個比它需要大得多的數組,然後將數組的子集視為數組。下面的程式碼就是個例子。

var vals [20]int
for i := 0; i < 5; i {
  vals[i] = i * i
}
subsetLen := 5

fmt.Println("The subset of our array has a length of:", subsetLen)

// Add a new item to our array
vals[subsetLen] = 123
subsetLen
fmt.Println("The subset of our array has a length of:", subsetLen)
登入後複製

在程式碼中,我們有一個長度為20 的數組,但由於我們只使用一個子集,程式碼中我們可以假定數組的長度是5,然後在我們向數組中添加一個新的項之後是6 。

這是(非常粗略地說)切片是如何工作的。它們包含一個具有設定大小的數組,就像我們前面的例子中的數組一樣,它的大小為 20。

它們也追蹤程式中使用的陣列的子集 - 這就是 append 屬性,它類似於上一個例子中的 subsetLen 變數。

最後,一個切片還有一個 capacity,類似於前面範例中我們的陣列的總長度(20)。這是很有用的,因為它會告訴你的子集在無法容納切片數組之前可以增長的大小。當發生這種情況時,需要指派一個新的數組,但所有這些邏輯都隱藏在 append 函數的後面。

簡而言之,使用 append 函數組合切片給我們一個非常類似於陣列的類型,但隨著時間的推移,它可以處理更多的元素。

我們再來看前面的例子,但這次我們將使用切片而不是陣列。

var vals []int
for i := 0; i < 5; i {
  vals = append(vals, i)
  fmt.Println("The length of our slice is:", len(vals))
  fmt.Println("The capacity of our slice is:", cap(vals))
}

// Add a new item to our array
vals = append(vals, 123)
fmt.Println("The length of our slice is:", len(vals))
fmt.Println("The capacity of our slice is:", cap(vals))

// Accessing items is the same as an array
fmt.Println(vals[5])
fmt.Println(vals[2])
登入後複製

我們仍然可以像數組一樣存取我們的切片中的元素,但是透過使用切片和 append 函數,我們不再需要考慮背後數組的大小。我們仍然可以透過使用 len 和 cap 函數來計算這些東西,但是我們不用擔心太多。簡潔吧?

回到測試

記住這一點,讓我們回顧前面的測試,看下什麼出錯了。

vals := make([]int, 5)
for i := 0; i < 5; i {
  vals = append(vals, i)
}
fmt.Println(vals)
登入後複製
登入後複製

當呼叫 make 時,我們允許最多傳入 3 個參數。第一個是我們指派的類型,第二個是類型的“長度”,第三個是類型的“容量”(這個參數是可選的)。

透過傳遞參數 make([]int, 5),我們告訴程式我們要建立一個長度為 5 的切片,在這種情況下,預設的容量與長度相同 - 本例中是 5。

雖然這可能看起來像我們想要的那樣,這裡的重要區別是我們告訴我們的切片,我們要將「長度」和「容量」設為5,假設你想要在初始的5 個元素之後加入新的元素,我們接著呼叫append 函數,那麼它會增加容量的大小,並且會在切片的最後添加新的元素。

如果在程式碼中加入一條 Println() 語句,你可以看到容量的變化。

vals := make([]int, 5)
fmt.Println("Capacity was:", cap(vals))
for i := 0; i < 5; i {
  vals = append(vals, i)
  fmt.Println("Capacity is now:", cap(vals))
}

fmt.Println(vals)
登入後複製

最後,我們最終得到 [0 0 0 0 0 0 1 2 3 4] 的輸出而不是希望的 [0 1 2 3 4]。

要如何修復它呢?好的,這有幾種方法,我們將講解兩種,你可以選取任何一種在你的場景中最有用的方法。

直接使用索引寫入而不是 append

#第一種修復是保留 make 呼叫不變,並且明確地使用索引來設定每個元素。這樣,我們就得到如下的程式碼:

vals := make([]int, 5)
for i := 0; i < 5; i {
  vals[i] = i
}
fmt.Println(vals)
登入後複製

在這種情況下,我們設定的值恰好與我們要使用的索引相同,但是你也可以獨立追蹤索引。

例如,如果你想要取得 map 的鍵,你可以使用下面的程式碼。

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  ret := make([]string, len(m))
  i := 0
  for key := range m {
    ret[i] = key
    i
  }
  return ret
}
登入後複製

這樣做很好,因為我們知道我們傳回的切片的長度將與 map 的長度相同,因此我們可以用該長度初始化我們的切片,然後將每個元素分配到適當的索引中。這種方法的缺點是我們必須追蹤 i,以便了解每個索引要設定的值。

這就讓我們引出了第二種方法…

使用 0 作為你的長度並指定容量

與其追蹤我們要新增的值的索引,我們可以更新我們的 make 調用,並在切片類型之後提供兩個參數。第一個,我們的新切片的長度將被設定為 0,因為我們還沒有添加任何新的元素到切片中。第二個,我們新切片的容量將被設定為 map 參數的長度,因為我們知道我們的切片最終會添加許多字串。

這會如前面的例子那樣仍舊會在背後建立相同的數組,但是現在當我們呼叫 append 時,它會將它們放在切片開始處,因為切片的長度是 0。

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  ret := make([]string, 0, len(m))
  for key := range m {
    ret = append(ret, key)
  }
  return ret
}
登入後複製
如果 append 處理它,為什麼我們還要擔心容量?

接下來你可能會問:「如果 append 函數可以為我增加切片的容量,那我們為什麼要告訴程式容量呢?」

事實是,在大多數情況下,你不必擔心這太多。如果它讓你的程式碼變得更複雜,只需用 var vals []int 初始化你的切片,然後讓 append 函數處理接下來的事。

但這種情況是不同的。它並不是聲明容量困難的例子,實際上這很容易確定我們的切片的最後容量,因為我們知道它將直接映射到提供的 map 中。因此,當我們初始化它時,我們可以聲明切片的容量,並免於讓我們的程式執行不必要的記憶體分配。

如果要查看額外的記憶體分配情況,請在 Go Playground 上執行以下程式碼。每次增加容量,程式都需要做一次記憶體分配。

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
    "mouse": struct{}{},
    "wolf": struct{}{},
    "alligator": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  var ret []string
  fmt.Println(cap(ret))
  for key := range m {
    ret = append(ret, key)
    fmt.Println(cap(ret))
  }
  return ret
}
登入後複製

現在將此與相同的程式碼進行比較,但具有預先定義的容量。

package main

import "fmt"

func main() {
  fmt.Println(keys(map[string]struct{}{
    "dog": struct{}{},
    "cat": struct{}{},
    "mouse": struct{}{},
    "wolf": struct{}{},
    "alligator": struct{}{},
  }))
}

func keys(m map[string]struct{}) []string {
  ret := make([]string, 0, len(m))
  fmt.Println(cap(ret))
  for key := range m {
    ret = append(ret, key)
    fmt.Println(cap(ret))
  }
  return ret
}
登入後複製

在第一個程式碼範例中,我們的容量從0 開始,然後增加到1、 2、 4, 最後是8,這意味著我們必須分配5 次數組,最後一個容納我們切片的陣列的容量是8,這比我們最終需要的要大。

另一方面,我們的第二個例子開始和結束都是相同的容量(5),它只需要在 keys() 函數的開頭分配一次。我們還避免了浪費任何額外的內存,並返回一個能放下這個數組的完美大小的切片。

不要過度優化

#如前所述,我通常不鼓勵任何人做這樣的小優化,但如果最後大小的效果真的很明顯,那麼我強烈建議你嘗試為切片設定適當的容量或長度。

這不僅有助於提高程式的效能,還可以透過明確說明輸入的大小和輸出的大小之間的關係來幫助澄清你的程式碼。

總結

本文並不是對切片或陣列之間差異的詳細討論,而是簡要介紹了容量和長度如何影響切片,以及它們在方案中的用途。


以上是在 Go 中使用切片的容量和長度的技巧的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

vscode需要什麼電腦配置 vscode需要什麼電腦配置 Apr 15, 2025 pm 09:48 PM

VS Code 系統要求:操作系統:Windows 10 及以上、macOS 10.12 及以上、Linux 發行版處理器:最低 1.6 GHz,推薦 2.0 GHz 及以上內存:最低 512 MB,推薦 4 GB 及以上存儲空間:最低 250 MB,推薦 1 GB 及以上其他要求:穩定網絡連接,Xorg/Wayland(Linux)

Linux體系結構:揭示5個基本組件 Linux體系結構:揭示5個基本組件 Apr 20, 2025 am 12:04 AM

Linux系統的五個基本組件是:1.內核,2.系統庫,3.系統實用程序,4.圖形用戶界面,5.應用程序。內核管理硬件資源,系統庫提供預編譯函數,系統實用程序用於系統管理,GUI提供可視化交互,應用程序利用這些組件實現功能。

vscode終端使用教程 vscode終端使用教程 Apr 15, 2025 pm 10:09 PM

vscode 內置終端是一個開發工具,允許在編輯器內運行命令和腳本,以簡化開發流程。如何使用 vscode 終端:通過快捷鍵 (Ctrl/Cmd ) 打開終端。輸入命令或運行腳本。使用熱鍵 (如 Ctrl L 清除終端)。更改工作目錄 (如 cd 命令)。高級功能包括調試模式、代碼片段自動補全和交互式命令歷史。

notepad怎麼運行java代碼 notepad怎麼運行java代碼 Apr 16, 2025 pm 07:39 PM

雖然 Notepad 無法直接運行 Java 代碼,但可以通過借助其他工具實現:使用命令行編譯器 (javac) 編譯代碼,生成字節碼文件 (filename.class)。使用 Java 解釋器 (java) 解釋字節碼,執行代碼並輸出結果。

git怎麼查看倉庫地址 git怎麼查看倉庫地址 Apr 17, 2025 pm 01:54 PM

要查看 Git 倉庫地址,請執行以下步驟:1. 打開命令行並導航到倉庫目錄;2. 運行 "git remote -v" 命令;3. 查看輸出中的倉庫名稱及其相應的地址。

vscode在哪寫代碼 vscode在哪寫代碼 Apr 15, 2025 pm 09:54 PM

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

Linux的主要目的是什麼? Linux的主要目的是什麼? Apr 16, 2025 am 12:19 AM

Linux的主要用途包括:1.服務器操作系統,2.嵌入式系統,3.桌面操作系統,4.開發和測試環境。 Linux在這些領域表現出色,提供了穩定性、安全性和高效的開發工具。

vscode終端命令不能用 vscode終端命令不能用 Apr 15, 2025 pm 10:03 PM

VS Code 終端命令無法使用的原因及解決辦法:未安裝必要的工具(Windows:WSL;macOS:Xcode 命令行工具)路徑配置錯誤(添加可執行文件到 PATH 環境變量中)權限問題(以管理員身份運行 VS Code)防火牆或代理限制(檢查設置,解除限制)終端設置不正確(啟用使用外部終端)VS Code 安裝損壞(重新安裝或更新)終端配置不兼容(嘗試不同的終端類型或命令)特定環境變量缺失(設置必要的環境變量)

See all articles