Go 語言 slice(切片) 的使用

序言

?在許多開發語言中,動態數組是必不可少的一個組成部分。在實際的開發中很少會使用到數組,因為對于數組的大小大多數情況下我們是不能事先就確定好的,所以他不夠靈活。動態數組通過提供自動擴容的機制,極大地提升了開發效率。這篇文章將介紹 Go 語言中的動態數組 — slice(切片)


1. 數據結構

?切片的組成如下, 每一個字段的含義如下:

  • Data:指向存儲元素數組的指針;
  • Len:該數組中元素的個數
  • Cap:該數組的容量大小
type SliceHeader struct {Data uintptrLen  intCap  int
}

如果你之前了解過 C++ 中的 vector 你會發現其實他們的思路是一樣的。一個實際存儲元素的切片如下:在這里插入圖片描述


2. 切片的初始化

a. 聲明但不初始化

?在 Go 語言中,如果你聲明一個切片但不初始化它,它的默認值是 nil。這意味著該切片沒有指向任何底層數組,長度和容量都為 0:

func main() {var slice []intsliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice))dataPtr := unsafe.Pointer(sliceHeader.Data)fmt.Printf("data = %v, len = %d, cap = %d\n", dataPtr, len(slice), cap(slice))
}

這里程序的輸出是:

data = , len = 0, cap = 0

b. 帶初始值初始化

?比起第一種方式,這個會在聲明的時候帶上字面值來初始化一個切片:

slice := []int{1, 2, 3} 
fmt.Printf("data = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))

此時,該切片的 lencap 會和元素數量保持一致,程序輸出:

data = [1 2 3], len = 3, cap = 3

c. 使用 make 初始化

?使用 make 來初始化一個切片也有兩者方式,首先是第一種:

slice := make([]int, 5)
fmt.Printf("data = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))

這代表創建一個切片,并且切片的 lencap 都是 5,切片的元素的值采用該類型的默認值:

data = [0 0 0 0 0], len = 5, cap = 5

第二種是將 lencap 分別賦值:

slice := make([]int, 2, 4)
fmt.Printf("data = %v, len = %d, cap = %d\n", slice, len(slice), cap(slice))

這代表創建一個切片,并且切片的len 是 2,cap 是 4:

data = [0 0], len = 2, cap = 4

這也是最常用的方式,使用 make 來預先分配內存大小可以避免后續添加元素時頻繁進行擴容操作!

d. 下標索引初始化

?Go 支持指定一個索引范圍來初始化一個切片,這是 C++vector 所不具備的能力,舉個例子:

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]
fmt.Println(slice)   // [2 3 4]

這里有一個長度為 5 的數組,現在使用索引范圍 [1, 4) 「左閉右開」 來初始化一個切片,甚至還可以這樣表達:

slice = arr[:4]   // 等價于 [0:4]
slice = arr[1:]   // 等價于 [1:len(arr) - 1]

現在有一個問題,使用索引初始化的切片和原數組是什么關系呢?換句話說這里是否涉及到了深拷貝呢?上代碼:

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // [2, 3, 4]
slice[0] = 0 // 修改值
fmt.Printf(“arr=%v\n”, arr)
fmt.Printf(“slice=%v\n”, slice)

輸出結果是:

arr=[1 0 3 4 5]
slice=[0 3 4]

上文中我們了解到了一個 slice 的結構是怎么樣的,結合輸出的結果,不難推斷出 data 指針指向了該數組的第二個位置,如下:
在這里插入圖片描述
這里 cap 的大小為什么是 4 怎么得到的呢 — cap = cap(arr) - 1


3. 切片的追加和擴容

a. 元素追加

?我們可以通過 append 操作來在切片最后追加元素,追加方式也有多種,舉個栗子:

slice := []int{1, 2}
slice = append(slice, 3)       // 追加一個元素
slice = append(slice, 4, 5, 6) // 追加多個元素
slice = append(slice, []int{7, 8}...) // 追加一個切片,...表示解包,不能省略

對于追加的操作,大家是否存在疑惑的點呢?我剛開始就不理解為什么在追加操作后對 slice 進行賦值的操作。這是因為 append 函數有一個重要的特性需要特別注意:它可能會返回一個新的底層數組(取決于是否進行擴容操作)。如果沒有進行賦值操作,那么 slice 還是指向原來的數組,舉個栗子:

在這里插入圖片描述

b. 切片擴容

?當切片的 len 等于 cap 時,在下一次 append 操作前就會進行一次擴容操作,擴容的邏輯如下:

func growslice(et *_type, old slice, cap int) slice {...newcap := old.capdoublecap := newcap + newcapif cap > doublecap {newcap = cap} else {if old.len < 1024 {newcap = doublecap} else {for 0 < newcap && newcap < cap {newcap += newcap / 4}if newcap <= 0 {newcap = cap}}}...
}

擴容的策略總結如下:
在這里插入圖片描述
可以看到 Go 語言增長容量的策略還是比較緩和的。


4. 切片易踩的坑

a. 參數傳遞類型傻傻分不清

?首先,我們先聊聊 C++ 當中的值傳遞和引用傳遞,就比如:

int main() {vector<int> vec = { 1, 2, 3, 4, 5 }funcJustForRead(vec)return 0;
}void funcJustForRead(vector<int> &vec) {...
}

對于某些只讀的場景,我們一般會傳引用,這樣就大大減少了拷貝帶來的開銷。在 Go 語言中好像并沒有 引用 的概念?但是仔細思考一下,Go 真的需要嗎:

func main() {slice := []int{ 1, 2, 3, 4, 5 }funcJustForRead(slice)
}func funcJustForRead(slice []int) {...
}

形參是實參的拷貝,slice 中指向元素的是 data 指針,即使形參和實參的 data 不一樣,但是兩者是指向的同一個數組,所以不需要引用。
?現在,這里有一個函數會對切片進行追加操作,我依然是值傳遞是否還是可行呢?舉個栗子(假設這里不涉及擴容操作):

func main() {slice := []int{1, 2}fmt.Println(slice)funcForAppend(slice)fmt.Println(slice)
}func funcForAppend(slice []int) {slice = append(slice, 3)
}

輸出結果是:

[1 2]
[1 2]

并沒有預想的新增一個值,為什么?上面我們介紹了,append 會返回一個新的切片,我們在 main 中使用的還是原來的切片。怎么解決呢?傳遞指針:

func main() {slice := make([]int, 0, 2)fmt.Println(slice)funcForAppend(&slice)fmt.Println(slice)
}func funcForAppend(slice *[]int) {*slice = append(*slice, 3)
}

b. len 和 cap 傻傻分不清

?之前我們談到過,可以預先分配好空間,可以避免后續的頻繁擴容操作,但是是否會有以下的誤解呢:

func main() {slice := make([]int, 5)slice = append(slice, 1)slice = append(slice, 1)slice = append(slice, 1)slice = append(slice, 1)fmt.Println(slice) // [0 0 0 0 0 1 1 1 1]
}

這里代表預先分配好 5 個空間,并且每一個空間使用該類型的默認值填充,當我們新加入元素時,是在已有的基礎上往后添加而不是從前開始覆蓋。正確的姿勢應該是這樣子的:

func main() {slice := make([]int, 0, 5)slice = append(slice, 1)slice = append(slice, 1)slice = append(slice, 1)slice = append(slice, 1)fmt.Println(slice) // [1 1 1 1]
}

5. 總結

?不僅只是會使用,并且知其所以然。我自認為這是非常重要的,這不僅能夠很大程度上減小我們在開發中犯錯的概念,還能夠有效提升代碼的質量。所以通過這篇 silce 帶我們走入 Go 的世界吧。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/905194.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905194.shtml
英文地址,請注明出處:http://en.pswp.cn/news/905194.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Qt5.14.2 鏈接 MySQL 8.4 遇到的問題

問題一: "Plugin caching_sha2_password could not be loaded: 找不到指定的模塊。 Library path is caching_sha2_password.dll QMYSQL: Unable to connect" 解決方法: alter user root@localhost identified with mysql_native_password by root;問題二: ERR…

Docker 部署 - Crawl4AI 文檔 (v0.5.x)

Docker 部署 - Crawl4AI 文檔 (v0.5.x) 快速入門 &#x1f680; 拉取并運行基礎版本&#xff1a; # 不帶安全性的基本運行 docker pull unclecode/crawl4ai:basic docker run -p 11235:11235 unclecode/crawl4ai:basic# 帶有 API 安全性啟用的運行 docker run -p 11235:1123…

開發工具分享: Web前端編碼常用的在線編譯器

1.OneCompiler 工具網址&#xff1a;https://onecompiler.com/ OneCompiler支持60多種編程語言&#xff0c;在全球有超過1280萬用戶&#xff0c;讓開發者可以輕易實現代碼的編寫、運行和共享。 OneCompiler的線上調試功能完全免費&#xff0c;對編程語言的覆蓋也很全&#x…

Docker-配置私有倉庫(Harbor)

配置私有倉庫&#xff08;Harbor&#xff09; 一、環境準備安裝 Docker 三、安裝docker-compose四、準備Harbor五、配置證書六、部署配置Harbor七、配置啟動服務八、定制本地倉庫九、測試本地倉庫 Harbor(港灣)&#xff0c;是一個用于 存儲 和 分發 Docker 鏡像的企業級 Regi…

關于高并發GIS數據處理的一點經驗分享

1、背景介紹 筆者過去幾年在參與某個大型央企的項目開發過程中,遇到了十分棘手的難題。其與我們平常接觸的項目性質完全不同。在一般的項目中,客戶一般只要求我們能夠通過桌面軟件對原始數據進行加工處理,將各類地理信息數據加工處理成地圖/場景和工作空間,然后再將工作空…

使用 DMM 測試 TDR

TDR&#xff08;時域反射計&#xff09;可能是實驗室中上升時間最快的儀器&#xff0c;但您可以使用直流歐姆表測試其準確性。 TDR 測量什么 在所有高速通道中&#xff0c;反射都很糟糕。我們嘗試設計一個通道來減少反射&#xff0c;這些反射都會導致符號間干擾 &#xff08;…

可視化圖解算法37:序列化二叉樹-II

1. 題目 描述 請實現兩個函數&#xff0c;分別用來序列化和反序列化二叉樹&#xff0c;不對序列化之后的字符串進行約束&#xff0c;但要求能夠根據序列化之后的字符串重新構造出一棵與原二叉樹相同的樹。 二叉樹的序列化(Serialize)是指&#xff1a;把一棵二叉樹按照某種遍…

【Python】Python常用數據類型詳解

Python常用數據類型詳解:增刪改查全掌握 Python作為一門簡潔高效的編程語言,其豐富的數據類型是構建程序的基礎。本文將詳細介紹數字、字符串、列表、元組、字典、集合這六種核心數據類型的特點及增刪改查操作,并附代碼示例,助你全面掌握數據操作技巧。 一、數字(Number)…

模板引用、組件基礎

#### 組件基礎 1. 定義和使用簡單組件 - ![alt text](./img/image-2.png) vue <!-- 在App.vue里 --> <script setup>import HelloWorld from ./components/HelloWorld.vue </script> <template><HelloWorld></HelloWorld></temp…

深入探索 RKNN 模型轉換之旅

在人工智能蓬勃發展的當下&#xff0c;邊緣計算領域的應用愈發廣泛。瑞芯微的 RKNN 技術在這一領域大放異彩&#xff0c;它能讓深度學習模型在其芯片平臺上高效運行。而在整個應用流程中&#xff0c;模型轉換是極為關鍵的一環&#xff0c;今天就讓我們一同深入這個神奇的 RKNN …

iframe嵌套網站的安全機制實現

背景&#xff1a; 公司內部有一套系統A部署在內網&#xff0c;這套系統嵌套了B網站&#xff08;也是內網&#xff09;&#xff0c;只有內網才能訪問。現在需要將這個A系統暴露到公網。B系統的安全策略比較低&#xff0c;想快速上線并提高B系統的安全性。 通過 Nginx 代理層 設置…

青少年編程與數學 02-019 Rust 編程基礎 08課題、字面量、運算符和表達式

青少年編程與數學 02-019 Rust 編程基礎 08課題、字面量、運算符和表達式 一、字面量1. 字面量的分類1.1 整數字面量1.2 浮點數字面量1.3 字符字面量1.4 字符串字面量1.5 布爾字面量1.6 字節數組字面量 2. 字面量的類型推斷3. 字面量的用途4. 字面量的限制字面量總結 二、運算符…

危化品安全員職業發展方向的優劣對比

以下是危化品安全員不同職業發展方向的優劣對比&#xff1a; 縱向晉升 優勢 職業路徑清晰&#xff1a;從危化品安全員逐步晉升為安全主管、安全經理、安全總監等管理職位&#xff0c;層級明確&#xff0c;有較為清晰的上升通道。管理能力提升&#xff1a;隨著職位上升&#x…

談AI/OT 的融合

過去的十幾年間&#xff0c;工業界討論最多的話題之一就是IT/OT 融合&#xff0c;現在&#xff0c;我們不僅要實現IT/OT 的融合&#xff0c;更要面向AI/OT 的融合。看起來不太靠譜&#xff0c;卻留給我們無限的想象空間。OT 領域的專家們不要再當“九斤老太”&#xff0c;指責這…

計算機網絡核心技術解析:從基礎架構到應用實踐

計算機網絡作為現代信息社會的基石&#xff0c;承載著全球數據交換與資源共享的核心功能。本文將從網絡基礎架構、核心協議、分層模型到實際應用場景&#xff0c;全面解析計算機網絡的核心技術&#xff0c;并結合行業最新趨勢&#xff0c;為讀者構建系統的知識體系。 一、計算機…

大規模數據并行排序策略(Parallel Sample Sort)

大規模數據并行排序策略 對于上億條大型記錄的并行排序&#xff0c;基于MPI的多節點環境&#xff0c;可以采用以下策略來充分利用內存和網絡資源&#xff1a; 推薦算法&#xff1a;樣本排序(Sample Sort) 樣本排序是大規模并行排序的高效算法&#xff0c;特別適合MPI環境&am…

o.redisson.client.handler.CommandsQueue : Exception occured. Channel

1&#xff0c; 版本 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>2.15.2</version> </dependency>2&#xff0c;問題 2025-05-12 10:46:47.436 ERROR 27780 --- [sson-netty-5-…

Kotlin跨平臺Compose Multiplatform實戰指南

Kotlin Multiplatform&#xff08;KMP&#xff09;結合 Compose Multiplatform 正在成為跨平臺開發的熱門選擇&#xff0c;它允許開發者用一套代碼構建 Android、iOS、桌面&#xff08;Windows/macOS/Linux&#xff09;和 Web 應用。以下是一個實戰指南&#xff0c;涵蓋核心概念…

【Jenkins簡單自動化部署案例:基于Docker和Harbor的自動化部署流程記錄】

摘要 本文記錄了作者使用Jenkins時搭建的一個簡單自動化部署案例&#xff0c;涵蓋Jenkins的Docker化安裝、Harbor私有倉庫配置、Ansible遠程部署等核心步驟。通過一個SpringBoot項目 (RuoYi) 的完整流程演示&#xff0c;從代碼提交到鏡像構建、推送、滾動更新&#xff0c;逐步實…

【Git】GitHub上傳圖片遇到的問題

一開始我直接在網頁上拖拽上傳&#xff0c;會說“網頁無法正常運作”。 采用git push上去&#xff1a; git clone https://github.com/your-username/your-repo-name.git cd your-repo-name git add . git commit -m "Add large images" git push origin main報錯&…