Go基礎(chǔ)Slice教程詳解
Go 語(yǔ)言切片(Slice)
Go 語(yǔ)言切片是對(duì)數(shù)組的抽象。
Go 數(shù)組的長(zhǎng)度不可改變,在特定場(chǎng)景中這樣的集合就不太適用,Go中提供了一種靈活,功能強(qiáng)悍的內(nèi)置類(lèi)型切片("動(dòng)態(tài)數(shù)組"),與數(shù)組相比切片的長(zhǎng)度是不固定的,可以追加元素,在追加時(shí)可能使切片的容量增大。
聲明Slice
帶有 T 類(lèi)型元素的切片由 []T 表示,其中T代表slice中元素的類(lèi)型。切片在內(nèi)部可由一個(gè)結(jié)構(gòu)體類(lèi)型表示,形式如下:
type slice struct { Length int Capacity int ZerothElement *byte }
可見(jiàn)一個(gè)slice由三個(gè)部分構(gòu)成:指針、長(zhǎng)度和容量。指針指向第一個(gè)slice元素對(duì)應(yīng)的底層數(shù)組元素的地址。長(zhǎng)度對(duì)應(yīng)slice中元素的數(shù)目;長(zhǎng)度不能超過(guò)容量,容量一般是從slice的開(kāi)始位置到底層數(shù)據(jù)的結(jié)尾位置。通過(guò)len和cap函數(shù)分別返回slice的長(zhǎng)度和容量。
創(chuàng)建Slice
直接聲明創(chuàng)建 slice
[]<元素類(lèi)型>{元素1, 元素2, …}
創(chuàng)建一個(gè)有 3 個(gè)整型元素的數(shù)組,并返回一個(gè)存儲(chǔ)在 c 中的切片引用。
c := []int{6, 7, 8}
make() 函數(shù)創(chuàng)建 slice
s1 := make([]int, 5) //長(zhǎng)度和容量都是 5 s2 := make([]int, 3, 10) //長(zhǎng)度是3,容量是10 fmt.Println(cap(s1),s2)
基于底層數(shù)組數(shù)組或切片創(chuàng)建
基于現(xiàn)有的切片或者數(shù)組創(chuàng)建,使用[i:j]這樣的操作符即可,她表示以i索引開(kāi)始,到j(luò)索引結(jié)束,截取原數(shù)組或者切片,創(chuàng)建而成的新切片,新切片的值包含原切片的i索引,但是不包含j索引。注意i和j都不能超過(guò)原切片或者數(shù)組的索引
slice :=[]int{1,2,3,4,5} slice1 := slice[:] slice2 := slice[0:] slice3 := slice[:5] fmt.Println(slice1) fmt.Println(slice2) fmt.Println(slice3)
新的切片和原數(shù)組或原切片共用的是一個(gè)底層數(shù)組,所以當(dāng)修改的時(shí)候,底層數(shù)組的值就會(huì)被改變,所以原切片的值也改變了。
slice := []int{1, 2, 3, 4, 5} newSlice := slice[1:3] newSlice[0] = 10 fmt.Println(slice) fmt.Println(newSlice)
切片與數(shù)組的區(qū)別
1.切片不是數(shù)組,但是切片底層指向數(shù)組
2.切片本身長(zhǎng)度是不一定的因此不可以比較,數(shù)組是可以的。
3.切片是變長(zhǎng)數(shù)組的替代方案,可以關(guān)聯(lián)到指向的底層數(shù)組的局部或者全部。
4.切片是引用傳遞(傳遞指針地址),而數(shù)組是值傳遞(拷貝值)
5.切片可以直接創(chuàng)建,引用其他切片或數(shù)組創(chuàng)建
6.如果多個(gè)切片指向相同的底層數(shù)組,其中一個(gè)值的修改會(huì)影響所有的切片
切片的修改
切片自己不擁有任何數(shù)據(jù)。它只是底層數(shù)組的一種表示。對(duì)切片所做的任何修改都會(huì)反映在底層數(shù)組中。
package main import ( "fmt" ) func main() { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} slice := arr[2:5] fmt.Println("array before", arr) for i := range slice { slice[i]++ } fmt.Println("array after ", arr) }
在上述程序的第 9 行,我們根據(jù)數(shù)組索引 2,3,4 創(chuàng)建一個(gè)切片 dslice 。for 循環(huán)將這些索引中的值逐個(gè)遞增。當(dāng)我們使用 for 循環(huán)打印數(shù)組時(shí),我們可以看到對(duì)切片的更改反映在數(shù)組中。該程序的輸出是
array before [0 1 2 3 4 5 6 7 8 9] array after [0 1 3 4 5 5 6 7 8 9]
當(dāng)多個(gè)切片共用相同的底層數(shù)組時(shí),每個(gè)切片所做的更改將反映在數(shù)組中。
package main import ( "fmt" ) func main() { array := [4]int{10, 20 ,30, 40} slice1 := array[:] slice2 := array[:] fmt.Println("array before change:", array) slice1[0] = 60 fmt.Println("array after modification to slice slice1:", array) slice2[1] = 70 fmt.Println("array after modification to slice slice2:", array) }
在 9 行中, numa [:] 缺少開(kāi)始和結(jié)束值。開(kāi)始和結(jié)束的默認(rèn)值分別為 0 和 len (numa) 。兩個(gè)切片 nums1 和 nums2 共享相同的數(shù)組。該程序的輸出是
array before change: [10 20 30 40] array after modification to slice slice1: [60 20 30 40] array after modification to slice slice2: [60 70 30 40]
從輸出中可以清楚地看出,當(dāng)切片共享同一個(gè)數(shù)組時(shí),每個(gè)所做的修改都會(huì)反映在數(shù)組中。
切片的長(zhǎng)度和容量
切片的長(zhǎng)度是切片中的元素?cái)?shù)。 切片的容量是從創(chuàng)建切片索引開(kāi)始的底層數(shù)組中元素?cái)?shù)。
package main import ( "fmt" ) func main() { fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"} fruitslice := fruitarray[1:3] fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) // length of is 2 and capacity is 6 }
在上面的程序中, fruitslice 是從 fruitarray 的索引 1 和 2 創(chuàng)建的。 因此, fruitlice 的長(zhǎng)度為 2 。
fruitarray 的長(zhǎng)度是 7。 fruiteslice 是從 fruitarray 的索引 1 創(chuàng)建的。因此, fruitslice 的容量是從 fruitarray 索引為 1 開(kāi)始,也就是說(shuō)從 orange 開(kāi)始,該值是 6 。因此, fruitslice 的容量為 6。該程序]輸出切片的 **長(zhǎng)度為 2 容量為 6 **。
切片可以重置其容量。任何超出這一點(diǎn)將導(dǎo)致程序運(yùn)行時(shí)拋出錯(cuò)誤。
package main import ( "fmt" ) func main() { fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"} fruitslice := fruitarray[1:3] fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) // length of is 2 and capacity is 6 fruitslice = fruitslice[:cap(fruitslice)] // re-slicing furitslice till its capacity fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice)) }
在上述程序的第 11 行中, fruitslice 的容量是重置的。以上程序輸出為,
length of slice 2 capacity 6 After re-slicing length is 6 and capacity is 6
追加切片元素
正如我們已經(jīng)知道數(shù)組的長(zhǎng)度是固定的,它的長(zhǎng)度不能增加。 切片是動(dòng)態(tài)的,使用 append 可以將新元素追加到切片上。append 函數(shù)的定義是
func append(s[]T,x ... T)[]T
append可以直接在切片尾部追加元素,也可以將一個(gè)切片追加到另一個(gè)切片尾部。
package main import ( "fmt" ) func main() { str := []string{"a", "b", "c"} fmt.Println("strs:", str, " length:", len(str), "capacity:", cap(str)) str = append(str, "d") fmt.Println("strs:", str, " length:", len(str), " capacity:", cap(str)) }
在上述程序中, str 的容量最初是 3。在第 10 行,我們給 str 添加了一個(gè)新的元素,并把 append(str, "d") 返回的切片賦值給 str。現(xiàn)在 str 的容量翻了一番,變成了 6。
strs: [a b c] length: 3 capacity: 3 strs: [a b c d] length: 4 capacity: 6
切片類(lèi)型的零值為 nil 。一個(gè) nil 切片的長(zhǎng)度和容量為 0。可以使用 append 函數(shù)將值追加到 nil 切片。
package main import ( "fmt" ) func main() { var strs []string //zero value of a slice is nil if strs == nil { fmt.Println("slice is nil going to append") strs = append(strs, "a", "b", "c") fmt.Println("string contents:",strs) } }
在上面的程序 names 是 nil,我們已經(jīng)添加 3 個(gè)字符串給 names 。該程序的輸出是
slice is nil going to append string contents: [a b c]
也可以使用 ... 運(yùn)算符將一個(gè)切片添加到另一個(gè)切片。
package main import ( "fmt" ) func main() { veggies := []string{"potatoes", "tomatoes", "brinjal"} fruits := []string{"oranges", "apples"} food := append(veggies, fruits...) fmt.Println("food:",food) }
在上述程序的第 10 行,food 是通過(guò) append(veggies, fruits...)
創(chuàng)建。程序的輸出為 food: [potatoes tomatoes brinjal oranges apples]
。
特別需要注意的是如果新切片的長(zhǎng)度未超過(guò)源切片的容量,則返回源切片,如果追加后的新切片長(zhǎng)度超過(guò)源切片的容量,則會(huì)返回全新的切片。
func main() { s1 := []int{1,2,3,4,5} fmt.Printf("s1:%p %d %d %v\n",s1,len(s1),cap(s1),s1) s2 :=append(s1,6) fmt.Printf("s3:%p %d %d %v\n",s2,len(s2),cap(s2),s2) s3 := s1[0:4] fmt.Printf("s3:%p %d %d %v\n",s3,len(s3),cap(s3),s3) s4 := append(s3,6) fmt.Printf("s4:%p %d %d %v\n",s4,len(s4),cap(s4),s4) fmt.Printf("s1:%p %d %d %v\n",s1,len(s1),cap(s1),s1) s5 := append(s4,8) fmt.Printf("s5:%p %d %d %v\n",s5,len(s5),cap(s5),s5) }
切片的函數(shù)傳遞
切片包含長(zhǎng)度、容量和指向數(shù)組第零個(gè)元素的指針。當(dāng)切片傳遞給函數(shù)時(shí),即使它通過(guò)值傳遞,指針變量也將引用相同的底層數(shù)組。因此,當(dāng)切片作為參數(shù)傳遞給函數(shù)時(shí),函數(shù)內(nèi)所做的更改也會(huì)在函數(shù)外可見(jiàn)。
package main import ( "fmt" ) func subtactOne(numbers []int) { for i := range numbers { numbers[i] -= 2 } } func main() { nos := []int{8, 7, 6} fmt.Println("slice before function call", nos) subtactOne(nos) // function modifies the slice fmt.Println("slice after function call", nos) // modifications are visible outside }
上述程序的行號(hào) 17 中,調(diào)用函數(shù)將切片中的每個(gè)元素遞減 2。在函數(shù)調(diào)用后打印切片時(shí),這些更改是可見(jiàn)的。如果你還記得,這是不同于數(shù)組的,對(duì)于函數(shù)中一個(gè)數(shù)組的變化在函數(shù)外是不可見(jiàn)的。
array before function call [8 7 6] array after function call [6 5 4]
多維切片
類(lèi)似于數(shù)組,切片可以有多個(gè)維度。
package main import ( "fmt" ) func main() { pls := [][]string { {"C", "C++"}, {"JavaScript"}, {"Go", "Rust"}, } for _, v1 := range pls { for _, v2 := range v1 { fmt.Printf("%s ", v2) } fmt.Printf("\n") } }
程序的輸出為,
C C++ JavaScript Go Rust copy
切片持有對(duì)底層數(shù)組的引用。只要切片在內(nèi)存中,數(shù)組就不能被垃圾回收。在內(nèi)存管理方面,這是需要注意的。讓我們假設(shè)我們有一個(gè)非常大的數(shù)組,我們只想處理它的一小部分。然后,我們由這個(gè)數(shù)組創(chuàng)建一個(gè)切片,并開(kāi)始處理切片。這里需要重點(diǎn)注意的是,在切片引用時(shí)數(shù)組仍然存在內(nèi)存中。
一種解決方法是使用copy 函數(shù) 來(lái)生成一個(gè)切片的副本。這樣我們可以使用新的切片,原始數(shù)組可以被垃圾回收。
func copy(dst,src[]T)int` package main import ( "fmt" ) func main() { s1 :=[]int{1,2,3,4,5} fmt.Println("s1",s1) s2 := make([]int,len(s1)) fmt.Println("s2",s2) copy(s2,s1) fmt.Println("s2",s2) s3 :=make([]int,len(s1)-2) copy(s3,s1); fmt.Println("s3",s3) s4 :=make([]int,len(s1)-1) copy(s4[1:3],s1[2:4]); fmt.Println("s4",s4) }
打印結(jié)果:
s1 [1 2 3 4 5] s2 [0 0 0 0 0] s2 [1 2 3 4 5] s3 [1 2 3] s4 [0 3 4 0]
總結(jié)
以上所述是小編給大家介紹的Go基礎(chǔ)Slice教程詳解,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- 理解Golang中的數(shù)組(array)、切片(slice)和map
- Go語(yǔ)言中的Slice學(xué)習(xí)總結(jié)
- Go語(yǔ)言入門(mén)教程之Arrays、Slices、Maps、Range操作簡(jiǎn)明總結(jié)
- Go語(yǔ)言中slice的用法實(shí)例分析
- Golang學(xué)習(xí)筆記(四):array、slice、map
- 深入解析Go語(yǔ)言編程中slice切片結(jié)構(gòu)
- 深入理解golang的基本類(lèi)型排序與slice排序
- 詳解Go中Map類(lèi)型和Slice類(lèi)型的傳遞
- 淺談golang slice 切片原理
- Golang slice切片操作之切片的追加、刪除、插入等
- Go語(yǔ)言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”
- golang中range在slice和map遍歷中的注意事項(xiàng)
- Go 中 slice 的 In 功能實(shí)現(xiàn)探索
- Golang中的Slice與數(shù)組及區(qū)別詳解
- golang語(yǔ)言如何將interface轉(zhuǎn)為int, string,slice,struct等類(lèi)型
- go 判斷兩個(gè) slice/struct/map 是否相等的實(shí)例
- Go基礎(chǔ)系列:Go切片(分片)slice詳解
相關(guān)文章
Golang中unicode碼和中文的互相轉(zhuǎn)換函數(shù)使用
這篇文章主要為大家介紹了Golang中unicode碼和中文的互相轉(zhuǎn)換函數(shù)使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09golang接口IP限流,IP黑名單,IP白名單的實(shí)例
這篇文章主要介紹了golang接口IP限流,IP黑名單,IP白名單的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go語(yǔ)言中的流程控制結(jié)構(gòu)和函數(shù)詳解
這篇文章主要介紹了Go語(yǔ)言中的流程控制結(jié)構(gòu)和函數(shù)詳解,本文詳細(xì)講解了if、goto、for、switch等控制語(yǔ)句,同時(shí)對(duì)函數(shù)相關(guān)知識(shí)做了講解,需要的朋友可以參考下2014-10-10300行代碼實(shí)現(xiàn)go語(yǔ)言即時(shí)通訊聊天室
本文主要介紹了300行代碼實(shí)現(xiàn)go語(yǔ)言即時(shí)通訊聊天室,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05golang使用net/rpc庫(kù)實(shí)現(xiàn)rpc
這篇文章主要為大家詳細(xì)介紹了golang如何使用net/rpc庫(kù)實(shí)現(xiàn)rpc,文章的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以參考一下2024-01-01Golang?Fasthttp選擇使用slice而非map?存儲(chǔ)請(qǐng)求數(shù)據(jù)原理探索
本文將從簡(jiǎn)單到復(fù)雜,逐步剖析為什么?Fasthttp?選擇使用?slice?而非?map,并通過(guò)代碼示例解釋這一選擇背后高性能的原因,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-02-02