數(shù)據(jù)類型的本質(zhì):固定內(nèi)存大小的別名。
數(shù)據(jù)類型的作用:編譯器預(yù)算對象或變量分配內(nèi)存空間的大小。
數(shù)組 array
數(shù)組是同一種數(shù)據(jù)類型的固定長度的序列虎忌,指向一段連續(xù)的內(nèi)存空間。
聲明
arr := [6]int{1,2,3,4,5,6}
特性
- 因為數(shù)組的內(nèi)存分布是連續(xù)的,所以數(shù)組訪問任一元素的效率很高宙址,O(1)。
- 元素類型和數(shù)組長度都是數(shù)組類型的一部分调卑,不同長度的數(shù)組是不同的類型抡砂。
- 數(shù)組是值類型,改變其副本的值不會改變原數(shù)組的值恬涧。
數(shù)組指針和指針數(shù)組
數(shù)組指針
即聲明一個指針變量注益,指向一個數(shù)組:
arr := [6]int{5:9} //第6個元素為9
var ptr *[6]int = &arr
需要注意的是,指針變量ptr
的類型是*[6]int
溯捆,也就是說它只能指向包含6個元素的整型數(shù)組丑搔,否則編譯報錯厦瓢。
指針數(shù)組
即數(shù)組的元素類型是指針。
x,y := 1,2
var arrPtr = [5]*int{1:&x,3:&y}
函數(shù)間傳遞數(shù)組
如果實參是一個非常大的數(shù)組啤月,因函數(shù)參數(shù)只有值傳遞煮仇,即需要重新拷貝變量,拷貝導(dǎo)致的內(nèi)存和性能開銷較大谎仲≌愕妫可將數(shù)組指針作為參數(shù)傳遞,拷貝開銷小郑诺,但也需要考慮到函數(shù)內(nèi)數(shù)組指針修改會影響原數(shù)組夹姥。
切片 slice
切片與數(shù)組類似,存放相同數(shù)據(jù)類型的元素辙诞,不同的是切片基于底層數(shù)組辙售,可按需擴(kuò)縮容。切片是底層數(shù)組的一個視圖飞涂,也可以說切片是對數(shù)組的抽象旦部。
切片的內(nèi)部實現(xiàn)中有三個變量,指針 ptr封拧,長度 len 和容量 cap志鹃。
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 指針,數(shù)據(jù)存儲在底層數(shù)組中泽西,而指針指向可以通過切片訪問到的第一個元素曹铃。
len int // 長度,我們只能訪問切片長度范圍內(nèi)的元素捧杉。
cap int // 容量陕见,表示可以擴(kuò)展的最大大小。容量必須大于等于長度味抖。
}
應(yīng)注意底層數(shù)組是可以被多個 slice 同時指向的评甜,因此對一個 slice 的元素進(jìn)行操作是有可能影響到其他 slice 的。
聲明與初始化
make 函數(shù)創(chuàng)建
s1 := make([]int,3,5) // 創(chuàng)建一個可用的空切片仔涩,長度為3忍坷,容量為5,容量也可以不傳
fmt.Println(s1) // [0 0 0]
字面量創(chuàng)建
s2 := []int{1,2,3,4,5} // 創(chuàng)建長度和容量都是5的整型切片
截取已有的數(shù)組或者切片創(chuàng)建
a := []int{1,2,3,4,5}
t := a[3:4]
截取得到的切片熔脂,和原切片或原數(shù)組共享底層數(shù)組佩研,但是兩者能訪問到底層數(shù)組的范圍是不同的。
截取獲得的新切片的長度和容量計算
對容量為 k 的切片執(zhí)行[i,j]
操作之后霞揉,獲得的新切片的長度和容量是j-i
和k-i
旬薯。
對容量為 k 的切片執(zhí)行[i,j,l]
操作之后,獲得的新切片的長度和容量是j-i
和l-i
适秩,其中第三個變量 l 用于限定新切片的容量绊序,j 和 l 必須在原數(shù)組或者原 slice 的容量范圍內(nèi)(小于等于 k)硕舆。
新切片與原數(shù)組或者切片的關(guān)系
截取得到的新切片和原數(shù)組或原切片是基于同一個底層數(shù)組的,所以當(dāng)修改的時候骤公,底層數(shù)組的值就會被改變抚官,原切片的值也隨之改變了。
可以把截取得到的新切片看做是原數(shù)組或原切片的一個視圖淋样。
但如果因為執(zhí)行 append 操作使得新 slice 底層數(shù)組擴(kuò)容耗式,移動到了新的位置胁住,兩者就不會相互影響了趁猴。所以,問題的關(guān)鍵在于兩者是否會共用底層數(shù)組彪见。
nil 切片和空切片
nil 切片
var s []int // 聲明了一個 nil 切片
fmt.Println(s == nil) // 輸出 true
fmt.Println(len(s), cap(s)) // 輸出:0 0
s = append(s, 1) // 使用 append 函數(shù)可以為 nil 切片增加元素
切片的零值是 nil儡司。因為切片是底層數(shù)組的引用,nil 切片指向底層數(shù)組的指針為 nil余指,即不指向任何底層數(shù)組捕犬。
空切片
s := make([]int, 0) // 1、使用 make 創(chuàng)建空的整型切片
s := []int{} // 2酵镜、使用切片字面量創(chuàng)建空的整型切片
fmt.Println(s == nil) // 輸出 false
fmt.Println(s) // 輸出:[]
fmt.Println(len(s), cap(s)) // 輸出:0 0
與 nil 切片一樣碉碉,空切片的長度和容量也都是 0,說明切片底層的數(shù)組大小為 0淮韭,是一個空數(shù)組(沒有分配任何的存儲空間)垢粮。
不管是使用 nil 切片還是空切片,對其調(diào)用內(nèi)置函數(shù) append靠粪、len 和 cap 的效果都是一樣的蜡吧。官方建議盡量使用 nil 切片。
nil slice 可以直接 append 占键,因為 append 最終都是調(diào)用 mallocgc 來向 Go 的內(nèi)存管理器申請到一塊內(nèi)存昔善,然后再賦給原來的 nil slice 或 empty slice。
增長/擴(kuò)容
使用內(nèi)建函數(shù) append 能夠幫我們處理切片增長的一些細(xì)節(jié)畔乙。
- append 可往切片追加一個或多個值君仆,然后返回一個新的切片。應(yīng)注意 Go 編譯器不允許調(diào)用了 append 函數(shù)后不使用返回值牲距。
- append 函數(shù)會使新的切片長度增加返咱。
- append 函數(shù)實際上是往底層數(shù)組添加元素。
- 新切片容量是否增長取決于原切片剩余容量和需要追加的元素數(shù)目嗅虏。當(dāng)剩余容量不足時洛姑,append 函數(shù)會創(chuàng)建一個新的數(shù)組并將原數(shù)組元素拷貝到新數(shù)組中,再追加新的值皮服。append 函數(shù)會智能地增加底層數(shù)組的容量楞艾,目前的算法是:當(dāng)數(shù)組容量小于等于1024時参咙,會成倍地增加;當(dāng)超過1024硫眯,增長因子變?yōu)?.25蕴侧,也就是說每次會增加25%的容量(這個說法是錯誤的,還有內(nèi)存對齊的操作)两入。
要注意的是净宵,通過截取創(chuàng)建的切片,如果切片剩余容量能存下 append 追加的元素裹纳,切片長度增長而不擴(kuò)容择葡,追加的元素會改變原切片或數(shù)組的值;如果不能存下追加的元素剃氧,切片長度增長并進(jìn)行擴(kuò)容敏储,擴(kuò)容操作為 append 函數(shù)創(chuàng)建一個新的底層數(shù)組,將原數(shù)組的值復(fù)制到新數(shù)組里朋鞍,再追加新的值已添,因為新切片與原切片或數(shù)組的底層數(shù)組不再相同,追加的元素不會改變原切片或數(shù)組的值滥酥,且后續(xù)兩個切片不再相互影響更舞。關(guān)鍵在于兩者是否會共用底層數(shù)組。
一般我們在創(chuàng)建新切片的時候坎吻,最好要讓新切片的長度和容量一樣缆蝉,這樣我們在追加操作的時候就會生成新的底層數(shù)組,和原有數(shù)組分離禾怠,就不會因為共用底層數(shù)組而引起奇怪問題返奉。
copy 函數(shù)
Go 提供了內(nèi)置函數(shù) copy,可以將一個切片復(fù)制到另一個切片吗氏。
func copy(dst, src []Type) int // 函數(shù)返回兩者長度的最小值
如果 dst 切片為 nil 切片芽偏,copy 之后,dst 切片仍為 nil弦讽。而 nil 切片 append 非空切片后污尉,變?yōu)榉强涨衅?/p>
copy 只會復(fù)制,不會追加往产。
函數(shù)間傳遞切片
切片在函數(shù)間以值的方式傳遞被碗。當(dāng)直接用切片作為函數(shù)參數(shù)時,可以改變切片的元素仿村,不能改變切片本身锐朴;想要改變切片本身,可以將改變后的切片返回蔼囊,函數(shù)調(diào)用者接收改變后的切片或者將切片指針作為函數(shù)參數(shù)焚志。
由于切片的尺寸很幸旅浴(在 64 位架構(gòu)的機(jī)器上,一個切片需要 24 字節(jié)的內(nèi)存:指針字段酱酬、長度和容量字段各需要 8 字節(jié))壶谒,在函數(shù)間復(fù)制和傳遞切片成本很低。
刪除切片中的元素
Go 沒有提供刪除切片元素的函數(shù)膳沽,可以通過截取和 append 函數(shù)實現(xiàn)汗菜。
s := []int{1, 2, 3, 4, 5, 6}
s = append(s[:2], s[3:]...) // 刪除索引為2的元素
切片垃圾回收
巨型 slice 產(chǎn)生的垃圾回收問題