陣列和切片
對比學習陣列和切片
陣列的長度是固定的,切片是可變長的。
陣列的型別字面量中必須有元素的型別和長度。陣列的長度在宣告的時候必須給定,並且之後不會再改變,陣列的長度是其型別的一部分。比如[1]string
和[2]string
就是2個不同的陣列型別。
切片的型別字面量中只有元素的型別,而沒有長度。切片的長度可以自動地隨著其中元素數量的增長而增長,但不會隨著元素數量的減少而減小。
可以把切片看作是對陣列的一層簡單封裝,因為在每個切片額底層資料結構中,一定會包含一個數組。陣列可以叫做切片的底層陣列,而切片可以看做是對陣列的某個連續片段的引用。
正因為“切片可以看作是陣列的某個連續片段的引用”,Go語言的切片型別屬於引用型別。同屬於引用型別的還有字典型別,通道型別,函式型別等;而Go語言的陣列型別則屬於值型別,同屬於值型別的還有基礎資料型別以及結構體型別。
Go語言裡不存在像Java等程式語言中令人困惑的“傳值或傳引用”問題。在Go語言中,我們判斷所謂的“傳值”或者“傳引用”只要看傳遞的值的型別就好了,如果傳遞的值的型別是值型別,那麼就是“傳值”,如果傳遞的值的型別是引用型別的,那麼就是“傳引用”。從傳遞成本的角度講,引用型別的值往往要比值型別的值低很多。
可以在陣列和切片之上應用“索引表示式”,得到的是某個元素。 也可以在陣列和切片上應用“切片表示式”,得到的是一個新的切片。
呼叫內建函式len,可以得到陣列和切片的長度。陣列的容量永遠等於其長度,都是不可變的。 呼叫內建函式cap,可以得到陣列和切片的容量。切片的容量是可變的,變化也是有規律可循的。
怎樣正確估算切片的長度和容量?
內建函式make可以建立切片。
make([]int,5,8)
第一個引數[]int
指明切片的型別,第二個引數5
指明切片的長度,第三個引數8
指明切片的容量。
切片的容量是什麼意思呢? 還記得上面說的嗎:陣列是切片的底層資料結構的一部分。切片的容量實際上代表了它的底層陣列的長度。 在切片s1上應用切片表示式,得到的新的切片s2的底層陣列和s1的底層陣列是一樣的。
回答問題:怎樣估算切片容量的增長?
一旦一個切片無法容納更多的元素,Go語言就會想辦法擴容。但是它並不會改變原來的切片,而是會生成一個容量更大的切片,然後把原有的元素和新元素一併拷貝到新的切片總。在一般情況下,可以簡單地認為新切片的容量(簡稱新容量)將會是原切片容量(簡稱原容量)的2倍。
但是當原切片的長度(原長度)大於或等於1024時,Go語言將會以原容量的1.25倍作為新容量的基準(新容量基準)。新容量基準將會被調整(不斷地與1.25相乘),直到結果不小於原長度與要追加的元素數量之和(簡稱新長度)。最終,新容量往往會比新長度大一些,當然,相等也是可能的。
另外,如果我們一次追加的元素過多,以至於新長度比原容量的2倍還要大,那麼新容量就會以新長度為基準。與前面的情況一樣,最終的容量在很多時候都要比新容量基準更大一些。
問題2:切片的底層陣列什麼時候會被替換?
確切的說,一個切片的底層陣列永遠不會被替換。為什麼?雖然在擴容的時候,Go語言一定會生成新的底層陣列,但是它同時也生成了新的切片。它是把新的切片作為了新底層陣列的視窗,而沒有對原切片及其底層陣列做任何改動。
請記住,在無需擴容的時候,append函式返回的是指向原底層陣列的新切片,而在需要擴容的時候,append函式返回的是指向新底層陣列的新切片。所以,嚴格來講,“擴容”這個詞用在這裡雖然形象但是不合適。
只要新長度不會超過原切片的原容量,那麼使用append函式對其追加元素的時候就不會引起擴容。這隻會使緊鄰切片視窗右邊(底層陣列中的)元素被新的元素替換掉。
問題3:如果有多個切片指向了同一個底層陣列,應該注意什麼?
初始時兩個切片引用同一個底層陣列,在後續操作中對某個切片的操作超出底層陣列的容量時,這兩個切片引用的就不是同一個陣列了
s1 :=[]int{1,2,3,4} s2 := s1[0:4] 就像這樣,這樣的話改變s2會影響s1,如何消除這種影響呢
可以用copy函式,或者自己深拷貝。
問題4:怎樣援用“擴容”的思想對切片進行“縮容”?
切片縮容之後還是會引用底層的原陣列,這有時候會造成大量縮容之後的多餘內容沒有被垃圾回收。可以使用新建一個數組然後copy的方式。