go語言教程之淺談陣列和切片的異同

nt1979發表於2021-09-09

Hello ,各位小夥伴大家好,我是小棧君,上次分享我們講到了Go語言關於專案工程結構的管理,本期的分享我們來講解一下關於go語言的陣列和切片的概念、用法和區別。

在go語言的程式開發過程中,我們避免不了陣列和切片。關於他們的用法和區別卻使得有的小夥伴感覺困惑。所以小棧君這裡也歸納和總結了關於陣列和切片的乾貨幫助小夥伴進行理解。

陣列的定義

陣列是具有相同唯一型別的一組已編號且長度固定的資料項序列,這種型別可以是任意的原始型別例如整形、字串或者自定義型別。

相對於去宣告 number0, number1, …, number99 的變數,使用陣列形式 numbers[0], numbers[1] …, numbers[99] 更加方便且易於擴充套件。

陣列元素可以透過索引(位置)來讀取(或者修改),索引從 0 開始,第一個元素索引為 0,第二個索引為 1,以此類推。
圖片描述

總體來講的話陣列就是同一種型別的固定長度的序列。

在go語言中陣列的定義是很簡單的。
圖片描述
如圖所示,我們定義了一個長度為2的陣列,在陣列定義的過程中,系統已經對這個陣列進行了初始化並分配了空間。所以我們如果想要進行賦值可以透過陣列名加上下標的方式進行賦值。但是值得注意的一點是我們並不能進行陣列的超長處理。這一點有別於java的陣列定義,java的定長陣列新增值後如果對於超出的值會有自動擴容功能。
圖片描述
但是在go語言中並沒有方法來進行增刪改查值,只有透過下標的方式,所以我們如果進行了越界處理編譯都會進行報錯。所以才入門的小夥伴們需要注意一下哦。陣列的下標在陣列的合法範圍之外就會出發訪問越界,會有panic出現。所以小棧君也是透過了一個例項給大家說明一下,因為編譯可能會不透過,所以我們巧妙的避開編譯器的編譯進行陣列的越界操作說明。
圖片描述
當然需要值得注意的一點是,陣列的長度也是陣列型別的一部分,因此var a [2]int 和 var b [3] int 是兩個不同的型別。

知識點來了,在go語言中的陣列是屬於值型別傳遞,當我們傳遞一個陣列到一個方法中,改變副本的值並不會修改到原本陣列的值。所以得到的陣列還是原來的樣子。
圖片描述
因此如果我們要對陣列進行值的修改的話,就只有進行指標操作啦~。
圖片描述

切片的概念

在go語言中陣列中長度不可以更改,所以在實際的應用環境中並不是非常實用,所以Go語言衍生出了一種靈活性強和功能更強大的內建型別,即為切片。

與上面所講的陣列相比,切片的長度是不固定的,並且切片是可以進行擴容。切片物件非常小,是因為它是隻有3個欄位的資料結構:一個是指向底層陣列的指標,一個是切片的長度,一個是切片的容量。這3個欄位,就是Go語言操作底層陣列的後設資料,有了它們,我們就可以任意的操作切片了。

當然,切片作為陣列的引用,所以切片屬於是引用型別,各位小夥伴可千萬要記住了哦。在切片的使用過程當中,我們可以透過遍歷陣列的方式進行對於切片的遍歷,我們也可以內建方法len對陣列或切片進行長度的計算。

當然我們也可以對切片的容量進行計算,之前有講過Go語言有豐富的內建庫提供給我們使用,所以我們也可以cap內建函式進行容量的計算。多個切片如果表示同一個陣列的片段,它們可以共享資料;因此一個切片和相關陣列的其他切片是共享儲存的,相反,不同的陣列總是代表不同的儲存。陣列實際上是切片的構建塊。
圖片描述

圖片描述
上面的例子小棧君分別用陣列和切片進行了測試,我們可以看到陣列的容量一旦確定後就無法進行更改,當我們的切片進行初始化,初始的容量是2,此時切片的容量和長度都是2,但是我透過內建的append方法進行了切片的增加。此時的切片的容量和長度都是4。此時我們並不能確定切片內建擴容的機制,但是隱約猜測是倍增。

言歸正傳,為了測試一下切片的擴容機制,所以小棧君又進行了切片的增加,此時,細心的小夥伴應該發現,這次小棧君一次性增加了兩個元素在一個append裡面,因為這是append方法是一個可變長度的傳值。這也是一個小知識點哦。

如果切片的底層陣列,沒有足夠的容量時,就會新建一個底層陣列,把原來陣列的值複製到新底層陣列裡,再追加新值,這時候就不會影響原來的底層陣列了。

append目前的演算法是:容量小於1000個時,總是成倍的增長,一旦容量超過1000個,增長因子設為1.25,也就是說每次會增加25%的容量。

之後我們發現切片的容量和長度發生了變化,如果說上次容量的擴張是4是我們猜測的倍數擴容方式,那麼這次我們就實錘了他的擴容機制就是倍增。而且在Go語言的容量和長度不一樣,所以我們也可以得出結論,就是在 0 <= len(arry) <= cap(slice)。

在我們宣告好切片後我們可以使用new或是make方法對切片進行初始化,當然小棧君也試著嘗試證明切片如果沒有進行初始化是會panic的。結果並沒有出現。因為如果slice沒有初始化,它僅僅相當於一個nil,長度和容量都為0,並不會panic。
圖片描述
小棧君也考慮到可能是因為沒有內建增加方法或是沒有報錯僅僅只是因為我後面利用對Carry陣列的切割進行賦值的緣故。所以不甘心又做了一次嘗試,定義好相應的切片後直接使用append方法,結果如下:
圖片描述
我們同樣可以透過上述的例子瞭解到切片的下標是左閉右開區間,因為我們carry陣列的內容如上圖所示, 我們最終得到的結果是IT乾貨棧,下標來講的話是屬於1。所以我們得到的結論和事實是一樣的。對於切片我們也有很多用法,如下圖所示:

圖片描述
下圖是Python中對於切片的操作,並且Python中的陣列更為靈活,在後面我為大家分享Python教程的時候會詳細分享哦。

圖片描述

切片的初始化

對於切片的初始化我們可以make方法和new方法
圖片描述
new(T) 為每個新的型別T分配一片記憶體,初始化為 0 並且返回型別為*T的記憶體地址:這種方法 返回一個指向型別為 T,值為 0 的地址的指標,它適用於值型別如陣列和結構體;它相當於 &T{}。

make(T) 返回一個型別為 T 的初始值,它只適用於3種內建的引用型別:切片、map 和 channel。

複製

因為在go語言的陣列是屬於值傳遞,之前的方法也證實了這一點,在方法傳遞值的時候系統會進行複製一份副本進行傳遞,如果需要改變的值的話就需要使用指標。但是在使用切片處理的時候,因為切片屬於引用傳遞,所以go語言有內建的函式copy方法進行值的複製。
圖片描述
上述的一個例子可以綜合說明幾點問題了,最開始我們定義了一個切片並且容量是2,內容是1和2,我們同樣定義了切片b但是並沒有做初始化處理。直接使用copy操作。使用copy操作的時候,小棧君也複製了原始碼出來,第一個是我們的資料來源,第二個引數傳遞我們的目標源。直接使用的話我們可以從結果看出並沒有成功。

所以接下來小棧君又進行了初始化操作。這裡舉例的目的是提醒各位,在操作切片的時候沒有初始化就相當於nil,最好是進行切片的初始化操作。在早期go語言的版本中,沒有初始化切片會直接報錯。接下來我又進行了再一次的複製操作並且列印出他們的地址和容量、長度。可以看出進行切片的複製是不會進行切片的擴容處理。而且他們分別指向了不同的地址說明複製成功。

好了,今天的分享就到這啦,如果你喜歡我的分享,麻煩你點選一個好看或贊,我是小棧君,不定期分享IT乾貨,包括但不限於區塊鏈、大資料、Python、go、等系列專題。希望與你共同成長。我們下期再見啦,拜了個拜~

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4822/viewspace-2824204/,如需轉載,請註明出處,否則將追究法律責任。

相關文章