Go語(yǔ)言之切片

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

創(chuàng)新互聯(lián)公司2013年成立,是專(zhuān)業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站制作、網(wǎng)站設(shè)計(jì)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元黃州做網(wǎng)站,已為上家服務(wù),為黃州各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575


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


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


切片對(duì)象非常小,是因?yàn)樗侵挥?3 個(gè)字段的數(shù)據(jù)結(jié)構(gòu):一個(gè)是指向底層數(shù)組的指針,一個(gè)是切片的長(zhǎng)度,一個(gè)是切片的容量。這 3 個(gè)字段,就是Go語(yǔ)言操作底層數(shù)組的元數(shù)據(jù),有了它們,我們就可以任意地操作切片了。


聲明和初始化


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


slice:=make([]int,5)


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


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


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


因?yàn)榍衅牡讓邮菙?shù)組,所以創(chuàng)建切片時(shí),如果不指定字面值的話,默認(rèn)值就是數(shù)組的元素的零值。這里我們所以指定了容量是 10 ,但是我們只能訪問(wèn) 5 個(gè)元素。因?yàn)榍衅拈L(zhǎng)度是 5 ,剩下的 5 個(gè)元素,需要切片擴(kuò)充后才可以訪問(wèn)。


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


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


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


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


slice:=[]int{4:1}


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


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


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


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


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


切片另外一個(gè)用處比較多的創(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索引開(kāi)始,到j索引結(jié)束,截取原數(shù)組或者切片,創(chuàng)建而成的新切片。新切片的值包含原切片的i索引,但是不包含j索引。對(duì)比Java的話,發(fā)現(xiàn)和String的subString方法很像。


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


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

newSlice[0] = 10

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


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


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


對(duì)于底層數(shù)組容量是k的切片slice[i:j]來(lái)說(shuō)
長(zhǎng)度:j-i
容量:k-i


比如我們上面的例子slice[1:3],長(zhǎng)度就是3-1=2,容量是5-1=4。不過(guò)代碼中我們計(jì)算的時(shí)候不用這么麻煩,因?yàn)镚o語(yǔ)言為我們提供了內(nèi)置的lencap函數(shù)來(lái)計(jì)算切片的長(zhǎng)度和容量。


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

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


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


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


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


使用切片


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


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


切片只能訪問(wèn)到其長(zhǎng)度內(nèi)的元素,訪問(wèn)超過(guò)長(zhǎng)度外的元素,會(huì)導(dǎo)致運(yùn)行時(shí)異常,與切片容量關(guān)聯(lián)的元素只能用于切片增長(zhǎng)。


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


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]


例子中,通過(guò)append函數(shù)為新創(chuàng)建的切片newSlice,追加了一個(gè)元素 10 。我們發(fā)現(xiàn)打印的輸出,原切片slice的第 4 個(gè)值也被改變了,變成了 10 。引起這種結(jié)果的原因是因?yàn)?code>newSlice有可用的容量,不會(huì)創(chuàng)建新的切片來(lái)滿(mǎn)足追加,所以直接在newSlice后追加了一個(gè)元素 10 。因?yàn)?code>newSlice和slice切片共用一個(gè)底層數(shù)組,所以切片slice的對(duì)應(yīng)的元素值也被改變了。


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


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


所以一般我們?cè)趧?chuàng)建新切片的時(shí)候,最好要讓新切片的長(zhǎng)度和容量一樣,這樣我們?cè)谧芳硬僮鞯臅r(shí)候就會(huì)生成新的底層數(shù)組,和原有數(shù)組分離,就不會(huì)因?yàn)楣灿玫讓訑?shù)組而引起奇怪問(wèn)題,因?yàn)楣灿脭?shù)組的時(shí)候修改內(nèi)容,會(huì)影響多個(gè)切片。


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


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


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


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


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

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


迭代切片


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


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


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


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


這里需要說(shuō)明的是range返回的是切片元素的復(fù)制,而不是元素的引用。


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


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

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


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


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


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


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

當(dāng)前題目:Go語(yǔ)言之切片
新聞來(lái)源:http://www.muchs.cn/article28/jpjgcp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供域名注冊(cè)、網(wǎng)站策劃、定制網(wǎng)站Google、外貿(mào)網(wǎng)站建設(shè)、電子商務(wù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站優(yōu)化排名