Go語言實戰筆記(五)| Go 切片

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org,第一時間看後續筆記。

切片也是一種資料結構,它和數組非常相似,因為他是圍繞動態數組的概念設計的,可以按需自動改變大小,使用這種結構,可以更方便的管理和使用資料集合。

內部實現

切片是基於數組實現的,它的底層是數組,它自己本身非常小,可以理解為對底層數組的抽象。因為機遇數組實現,所以它的底層的記憶體是連續非配的,效率非常高,還可以通過索引獲得資料,可以迭代以及記憶體回收最佳化的好處。

切片對象非常小,是因為它是只有3個欄位的資料結構:一個是指向底層數組的指標,一個是切片的長度,一個是切片的容量。這3個欄位,就是Go語言操作底層數組的中繼資料,有了它們,我們就可以任意的操作切片了。

聲明和初始化

切片建立的方式有好幾種,我們先看下最簡潔的make方式。

1
slice:=make([]int,5)

使用內建的make函數時,需要傳入一個參數,指定切片的長度,例子中我們使用的時5,這時候切片的容量也是5。當然我們也可以單獨指定切片的容量。

1
slice:=make([]int,5,10)

這時,我們建立的切片長度是5,容量是10,需要注意的這個容量10其實對應的是切片底層數組的。

因為切片的底層是數組,所以建立切片時,如果不指定字面值的話,預設值就是數組的元素的零值。這裡我們所以指定了容量是10,但是我們職能訪問5個元素,因為切片的長度是5,剩下的5個元素,需要切片擴充後才可以訪問。

容量必須>=長度,我們是不能建立長度大於容量的切片的。

還有一種建立切片的方式,是使用字面量,就是指定初始化的值。

1
slice:=[]int{1,2,3,4,5}

有沒有發現,是建立數組非常像,只不過不用制定[]中的值,這時候切片的長度和容量是相等的,並且會根據我們指定的字面量推匯出來。當然我們也可以像數組一樣,只初始化某個索引的值:

1
slice:=[]int{4:1}

這是指定了第5個元素為1,其他元素都是預設值0。這時候切片的長度和容量也是一樣的。這裡再次強調一下切片和數組的微小差別。

1234
//數組array:=[5]int{4:1}//切片slice:=[]int{4:1}

切片還有nil切片和空切片,它們的長度和容量都是0,但是它們指向底層數組的指標不一樣,nil切片意味著指向底層數組的指標為nil,而空切片對應的指標是個地址。

1234
//nil切片var nilSlice []int//空切片slice:=[]int{}

nil切片表示不存在的切片,而空切片表示一個空集合,它們各有用處。

切片另外一個用處比較多的建立是基於現有的數組或者切片建立。

12345678
slice := []int{1, 2, 3, 4, 5}slice1 := slice[:]slice2 := slice[0:]slice3 := slice[:5]fmt.Println(slice1)fmt.Println(slice2)fmt.Println(slice3)

基於現有的切片或者數組建立,使用[i:j]這樣的操作符即可,她表示以i索引開始,到j索引結束,截取原數組或者切片,建立而成的新切片,新切片的值包含原切片的i索引,但是不包含j索引。對比Java的話,發現和String的subString方法很像。

i如果省略,預設是0;j如果省略預設是原數組或者切片的長度,所以例子中的三個新切片的值是一樣的。這裡注意的是ij都不能超過原切片或者數組的索引。

1234567
slice := []int{1, 2, 3, 4, 5}newSlice := slice[1:3]newSlice[0] = 10fmt.Println(slice)fmt.Println(newSlice)

這個例子證明了,新的切片和原切片共用的是一個底層數組,所以當修改的時候,底層數組的值就會被改變,所以原切片的值也改變了。當然對於基於數組的切片也一樣的。

我們基於原數組或者切片建立一個新的切片後,那麼新的切片的大小和容量是多少呢?這裡有個公式:

123
對於底層數組容量是k的切片slice[i:j]來說長度:j-i容量:k-i

比如我們上面的例子slice[1:3],長度就是3-1=2,容量是5-1=4。不過代碼中我們計算的時候不用這麼麻煩,因為Go語言為我們提供了內建的lencap函數來計算切片的長度和容量。

1234
slice := []int{1, 2, 3, 4, 5}newSlice := slice[1:3]fmt.Printf("newSlice長度:%d,容量:%d",len(newSlice),cap(newSlice))

以上基於一個數組或者切片使用2個索引建立新切片的方法,此外還有一種3個索引的方法,第3個用來限定新切片的容量,其用法為slice[i:j:k]

12
slice := []int{1, 2, 3, 4, 5}newSlice := slice[1:2:3]

這樣我們就建立了一個長度為2-1=1,容量為3-1=2的新切片,不過第三個索引,不能超過原切片的最大索引值5。

使用切片

使用切片,和使用數組一樣,通過索引就可以擷取切片對應元素的值,同樣也可以修改對應元素的值。

1234
slice := []int{1, 2, 3, 4, 5}fmt.Println(slice[2]) //擷取值slice[2] = 10 //修改值fmt.Println(slice[2]) //輸出10

切片只能訪問到其長度內的元素,訪問超過長度外的元素,會導致運行時異常,與切片容量關聯的元素只能用於切片增長。

我們前面講了,切片算是一個動態數組,所以它可以按需增長,我們使用內建append函數即可。append函數可以為一個切片追加一個元素,至於如何增加、返回的是原切片還是一個新切片、長度和容量如何改變這些細節,append函數都會幫我們自動處理。

12345678910
slice := []int{1, 2, 3, 4, 5}newSlice := slice[1:3]newSlice=append(newSlice,10)fmt.Println(newSlice)fmt.Println(slice)//Output[2 3 10][1 2 3 10 5]

例子中,通過append函數為新建立的切片newSlice,追加了一個元素10,我們發現列印的輸出,原切片slice的第4個值也被改變了,變成了10。引起這種結果的原因是因為newSlice有可用的容量,不會建立新的切片來滿足追加,所以直接在newSlice後追加了一個元素10,因為newSliceslice切片共用一個底層數組,所以切片slice的對應的元素值也被改變了。

這裡newSlice新追加的第3個元素,其實對應的是slice的第4個元素,所以這裡的追加其實是把底層數組的第4個元素修改為10,然後把newSlice長度調整為3。

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

所以一般我們在建立新切片的時候,最好要讓新切片的長度和容量一樣,這樣我們在追加操作的時候就會產生新的底層數組,和原有數組分離,就不會因為共用底層數組而引起奇怪問題,因為共用數組的時候修改內容,會影響多個切片。

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

內建的append也是一個可變參數的函數,所以我們可以同時追加好幾個值。

1
newSlice=append(newSlice,10,20,30)

此外,我們還可以通過...操作符,把一個切片追加到另一個切片裡。

123456
slice := []int{1, 2, 3, 4, 5}newSlice := slice[1:2:3]newSlice=append(newSlice,slice...)fmt.Println(newSlice)fmt.Println(slice)

迭代切片

切片是一個集合,我們可以使用 for range 迴圈來迭代它,列印其中的每個元素以及對應的索引。

1234
slice := []int{1, 2, 3, 4, 5}for i,v:=range slice{fmt.Printf("索引:%d,值:%d\n",i,v)}

如果我們不想要索引,可以使用_來忽略它,這是Go語言的用法,很多不需要的函數等傳回值,都可以忽略。

1234
   slice := []int{1, 2, 3, 4, 5}for _,v:=range slice{fmt.Printf("值:%d\n",v)}

這裡需要說明的是range返回的是切片元素的複製,而不是元素的引用。

除了for range迴圈外,我們也可以使用傳統的for迴圈,配合內建的len函數進行迭代。

1234
slice := []int{1, 2, 3, 4, 5}for i := 0; i < len(slice); i++ {fmt.Printf("值:%d\n", slice[i])}

在函數間傳遞切片

我們知道切片是3個欄位構成的結構類型,所以在函數間以值的方式傳遞的時候,佔用的記憶體非常小,成本很低。在傳遞複製切片的時候,其底層數組不會被複製,也不會受影響,複製只是複製的切片本身,不涉及底層數組。

1234567891011
func main() {slice := []int{1, 2, 3, 4, 5}fmt.Printf("%p\n", &slice)modify(slice)fmt.Println(slice)}func modify(slice []int) {fmt.Printf("%p\n", &slice)slice[1] = 10}

列印的輸出如下:

123
0xc4200820600xc420082080[1 10 3 4 5]

仔細看,這兩個切片的地址不一樣,所以可以確認切片在函數間傳遞是複製的。而我們修改一個索引的值後,發現原切片的值也被修改了,說明它們共用一個底層數組。

在函數間傳遞切片非常高效,而且不需要傳遞指標和處理複雜的文法,只需要複製切片,然後根據自己的業務修改,最後傳遞迴一個新的切片副本即可,這也是為什麼函數間傳遞參數,使用切片,而不是數組的原因。

關於多維切片就不介紹了,還有多維陣列,一來它和普通的切片數組一樣,只不過是多個一維組成的多維;二來我壓根不推薦用多維切片和數組,可讀性不好,結構不夠清晰,容易出問題。

《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org,第一時間看後續筆記。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.