溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go語言之切片

發(fā)布時間:2020-07-19 19:37:52 來源:網(wǎng)絡(luò) 閱讀:1350 作者:baby神 欄目:編程語言

切片也是一種數(shù)據(jù)結(jié)構(gòu),它和數(shù)組非常相似,因為他是圍繞動態(tài)數(shù)組的概念設(shè)計的,可以按需自動改變大小,使用這種結(jié)構(gòu),可以更方便地管理和使用數(shù)據(jù)集合。


內(nèi)部實現(xiàn)


切片是基于數(shù)組實現(xiàn)的,它的底層是數(shù)組,它自己本身非常小,可以理解為對底層數(shù)組的抽象。因為機遇數(shù)組實現(xiàn),所以它的底層的內(nèi)存是連續(xù)非配的,效率非常高。它還有可以通過索引獲得數(shù)據(jù)、可以迭代以及垃圾回收優(yōu)化的好處。


切片對象非常小,是因為它是只有 3 個字段的數(shù)據(jù)結(jié)構(gòu):一個是指向底層數(shù)組的指針,一個是切片的長度,一個是切片的容量。這 3 個字段,就是Go語言操作底層數(shù)組的元數(shù)據(jù),有了它們,我們就可以任意地操作切片了。


聲明和初始化


切片創(chuàng)建的方式有好幾種,我們先看下最簡潔的make方式。


slice:=make([]int,5)


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


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


這時,我們創(chuàng)建的切片長度是 5 ,容量是 10 。需要注意的這個容量 10 其實對應的是切片底層數(shù)組的。


因為切片的底層是數(shù)組,所以創(chuàng)建切片時,如果不指定字面值的話,默認值就是數(shù)組的元素的零值。這里我們所以指定了容量是 10 ,但是我們只能訪問 5 個元素。因為切片的長度是 5 ,剩下的 5 個元素,需要切片擴充后才可以訪問。


容量必須>=長度,我們是不能創(chuàng)建長度大于容量的切片的。


還有一種創(chuàng)建切片的方式,是使用字面量,就是指定初始化的值。


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


有沒有發(fā)現(xiàn),跟創(chuàng)建數(shù)組非常像,只不過不用制定[]中的值。這時候切片的長度和容量是相等的,并且會根據(jù)我們指定的字面量推導出來。當然我們也可以像數(shù)組一樣,只初始化某個索引的值:


slice:=[]int{4:1}


這是指定了第 5 個元素為 1 ,其他元素都是默認值 0 。這時候切片的長度和容量也是一樣的。這里再次強調(diào)一下切片和數(shù)組的微小差別。


//數(shù)組
array:=[5]int{4:1}
//切片
slice:=[]int{4:1}


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


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


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


切片另外一個用處比較多的創(chuàng)建是基于現(xiàn)有的數(shù)組或者切片創(chuàng)建。


slice := []int{1, 2, 3, 4, 5}
slice1 := slice[:]
slice2 := slice[0:]
slice3 := slice[:5]

fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)


基于現(xiàn)有的切片或者數(shù)組創(chuàng)建,使用[i:j]這樣的操作符即可。它表示以i索引開始,到j索引結(jié)束,截取原數(shù)組或者切片,創(chuàng)建而成的新切片。新切片的值包含原切片的i索引,但是不包含j索引。對比Java的話,發(fā)現(xiàn)和String的subString方法很像。


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


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

newSlice[0] = 10

fmt.Println(slice)
fmt.Println(newSlice)


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


我們基于原數(shù)組或者切片創(chuàng)建一個新的切片后,那么新的切片的大小和容量是多少呢?這里有個公式:


對于底層數(shù)組容量是k的切片slice[i:j]來說
長度:j-i
容量:k-i


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


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

fmt.Printf("newSlice長度:%d,容量:%d",len(newSlice),cap(newSlice))


以上是基于一個數(shù)組或者切片使用 2 個索引創(chuàng)建新切片的方法。此外還有一種 3 個索引的方法,第 3 個用來限定新切片的容量,其用法為slice[i:j:k]。


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


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


使用切片


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


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


切片只能訪問到其長度內(nèi)的元素,訪問超過長度外的元素,會導致運行時異常,與切片容量關(guān)聯(lián)的元素只能用于切片增長。


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


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函數(shù)為新創(chuàng)建的切片newSlice,追加了一個元素 10 。我們發(fā)現(xiàn)打印的輸出,原切片slice的第 4 個值也被改變了,變成了 10 。引起這種結(jié)果的原因是因為newSlice有可用的容量,不會創(chuàng)建新的切片來滿足追加,所以直接在newSlice后追加了一個元素 10 。因為newSliceslice切片共用一個底層數(shù)組,所以切片slice的對應的元素值也被改變了。


這里newSlice新追加的第 3 個元素,其實對應的是slice的第 4 個元素,所以這里的追加其實是把底層數(shù)組的第4個元素修改為 10 ,然后把newSlice長度調(diào)整為 3 。


如果切片的底層數(shù)組沒有足夠的容量時,就會新建一個底層數(shù)組,把原來數(shù)組的值復制到新底層數(shù)組里,再追加新值,這時候就不會影響原來的底層數(shù)組了。


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


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


內(nèi)置的append也是一個可變參數(shù)的函數(shù),所以我們可以同時追加好幾個值。


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


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


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

newSlice=append(newSlice,slice...)
fmt.Println(newSlice)
fmt.Println(slice)


迭代切片


切片是一個集合,我們可以使用for range循環(huán)來迭代它,打印其中的每個元素以及對應的索引。


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


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


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


這里需要說明的是range返回的是切片元素的復制,而不是元素的引用。


除了for range循環(huán)外,我們也可以使用傳統(tǒng)的for循環(huán),配合內(nèi)置的len函數(shù)進行迭代。


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

在函數(shù)間傳遞切片


我們知道切片是 3 個字段構(gòu)成的結(jié)構(gòu)類型,所以在函數(shù)間以值的方式傳遞的時候,占用的內(nèi)存非常小,成本很低。在傳遞復制切片的時候,其底層數(shù)組不會被復制,也不會受影響,復制只是復制的切片本身,不涉及底層數(shù)組。


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
}


打印的輸出如下:


0xc420082060
0xc420082080
[1 10 3 4 5]


仔細看,這兩個切片的地址不一樣,所以可以確認切片在函數(shù)間傳遞是復制的。而我們修改一個索引的值后,發(fā)現(xiàn)原切片的值也被修改了,說明它們共用一個底層數(shù)組。


在函數(shù)間傳遞切片非常高效,而且不需要傳遞指針和處理復雜的語法,只需要復制切片,然后根據(jù)自己的業(yè)務(wù)修改,最后傳遞回一個新的切片副本即可。這也是為什么函數(shù)間使用切片傳遞參數(shù),而不是數(shù)組的原因。


關(guān)于多維切片就不介紹了,還有多維數(shù)組,一來它和普通的切片數(shù)組一樣,只不過是多個一維組成的多維;二來我壓根不推薦用多維切片和數(shù)組,可讀性不好,結(jié)構(gòu)不夠清晰,容易出問題。


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI