溫馨提示×

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

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

Go36-7-數(shù)組和切片

發(fā)布時(shí)間:2020-08-07 14:26:19 來(lái)源:網(wǎng)絡(luò) 閱讀:375 作者:騎士救兵 欄目:編程語(yǔ)言

數(shù)組和切片

數(shù)組(array)類型和切片(slice)類型:
相同:都屬于集合類的類型,它們的值都可以用來(lái)存儲(chǔ)某一種類型的值(或者說(shuō)元素)。
不同:數(shù)組的長(zhǎng)度是固定的,而切片是可變長(zhǎng)的。

長(zhǎng)度
數(shù)組的長(zhǎng)度在聲明的時(shí)候必須確定,并且之后不會(huì)再變。長(zhǎng)度是其類型的一部分。
比如:[1]string 和 [2]string 是兩個(gè)不同的類型。
切片的長(zhǎng)度是可以隨著其中元素的增長(zhǎng)而增長(zhǎng)的,但是不會(huì)隨著元素的減少而減少。

底層數(shù)組
可以把切片看做是對(duì)數(shù)組的一層簡(jiǎn)單的封裝,每個(gè)切片的底層數(shù)據(jù)結(jié)構(gòu)中,一定會(huì)包含一個(gè)數(shù)組。這個(gè)數(shù)組可以被叫做切片的底層數(shù)組。而切片可以被看做是對(duì)數(shù)組的某個(gè)連續(xù)片段的引用。

值類型、引用類型

切片屬于引用類型,數(shù)組屬于值類型
引用類型:

  • 切片
  • 字典
  • 管道
  • 函數(shù)

值類型:

  • 數(shù)組
  • 結(jié)構(gòu)體

長(zhǎng)度和容量

數(shù)組和切片都用長(zhǎng)度和容量。調(diào)用len函數(shù),可以得到長(zhǎng)度,調(diào)用cap函數(shù)可以得到容量。
數(shù)組的容量永遠(yuǎn)等于長(zhǎng)度,并且是不可變的。
關(guān)于切片的容量和長(zhǎng)度,看下面的例子:

package main

import "fmt"

func main() {
    s1 := make([]int, 5)
    fmt.Println(len(s1), cap(s1), s1)
    s2 := make([]int, 5, 8)
    fmt.Println(len(s2), cap(s2), s2)
}

先用make聲明了一個(gè)[]int類型的變量s1,并且傳遞了一個(gè)第二個(gè)參數(shù)5。指明了切片的長(zhǎng)度。
用同樣了方式聲明了切片s2,這次多傳遞了一個(gè)參數(shù)8,指明了切片的容量。
s1的長(zhǎng)度是5,通過(guò)聲明指定。容量也是5,聲明時(shí)沒(méi)有指定容量,就和長(zhǎng)度一致。
s2的長(zhǎng)度是5,通過(guò)聲明指定。容量是8,也是通過(guò)聲明進(jìn)行指定。
下面的切片表達(dá)式,可以把s2擴(kuò)展到其當(dāng)前最大容量:

s2[0:cap(s2)]

擴(kuò)容

當(dāng)切片無(wú)法容納更多的元素時(shí),Go語(yǔ)言就會(huì)對(duì)切片進(jìn)行擴(kuò)容。擴(kuò)容不會(huì)改變?cè)瓉?lái)的切片,而是會(huì)生成一個(gè)容量更大的切片,然后把原有的元素和新元素拷貝到新切片中。
一般情況下,擴(kuò)容會(huì)把新切片的容量(新容量)變成原來(lái)切片容量(原容量)的2倍。
當(dāng)切片的長(zhǎng)度大于或等于1024是,擴(kuò)容是以1.25倍來(lái)增加的。
驗(yàn)證一下上面的擴(kuò)容策略:

package main

import "fmt"

func main() {
    s1 := make([]int, 3)
    fmt.Println(len(s1), cap(s1), s1)  // 當(dāng)前容量3
    s1 = append(s1, 1)
    fmt.Println(len(s1), cap(s1), s1)  // 容量翻倍,變成6
    s2 := make([]int, 1023)
    fmt.Println(len(s2), cap(s2))  // 當(dāng)前容量1023
    s2 = append(s2, 1)
    fmt.Println(len(s2), cap(s2))  // 看著像翻倍,不過(guò)是1024的翻倍2048
    s3 := make([]int, 1024)
    s3 = append(s3, 1)
    fmt.Println(len(s3), cap(s3))  // 容量變?yōu)?.25倍
}
/* 執(zhí)行結(jié)果
PS G:\Steed\Documents\Go\src\Go36\article07\example02> go run main.go
3 3 [0 0 0]
4 6 [0 0 0 1]
1023 1023
1024 2048
1025 1280
PS G:\Steed\Documents\Go\src\Go36\article07\example02>
*/

驗(yàn)證下來(lái),似乎有一點(diǎn)小偏差。
另外,如果一次追加的元素過(guò)多,按上面的規(guī)則做一次擴(kuò)容不夠,最終還是會(huì)擴(kuò)容到一個(gè)比需要的容量大一些或者正好的容量,不過(guò)具體情況有點(diǎn)復(fù)雜??梢宰约涸囋嚳矗旅娴睦右淮慰梢酝衅镒芳哟罅康脑兀?/p>

package main

import "fmt"

func main() {
    s1 := make([]int, 5, 8)
    var s []int
    s = append(s1, make([]int, 88-5)...)
    fmt.Println(len(s), cap(s))
    s2 := make([]int, 1024)
    s = append(s2, make([]int, 2048-1024)...)
    fmt.Println(len(s), cap(s))
}

不必太在意切片“擴(kuò)容”策略中的一些細(xì)節(jié),只要能夠理解它的基本規(guī)律,并可以進(jìn)行近似的估算就可以了。
不過(guò)如果有興趣,更多細(xì)節(jié)可參見(jiàn)runtime包中slice.go文件里的growslice及相關(guān)函數(shù)的具體實(shí)現(xiàn)。

替換底層數(shù)組

無(wú)需擴(kuò)容時(shí),append函數(shù)返回的是指向原底層數(shù)組的新切片。其實(shí)就是底層數(shù)組還在那,切片的下標(biāo)往后加了幾位,可以指向到底層數(shù)組后面更多的元素了。
需要擴(kuò)容時(shí),append函數(shù)返回的是指向新底層數(shù)組的新切片。會(huì)創(chuàng)建一個(gè)更大容量的新的底層數(shù)組,將元素從原切片復(fù)制到新的底層數(shù)組,然后追加新元素。
希望下面的例子可以說(shuō)明這里的問(wèn)題:

package main

import "fmt"

func main() {
    a := [...]int{1,2,3,4,5}  // 這是一個(gè)數(shù)組,將作為下面切片的底層數(shù)組
    s1 := a[:3]
    s1 = append(s1, 11)  // 未發(fā)生擴(kuò)容
    fmt.Println(s1, a)  // s1的新底層數(shù)組還是a,往s1末尾添加元素,將覆蓋a里原來(lái)的值
    s2 := a[:3]
    s2 = append(s2, 21, 22, 23, 24, 25)  // 容量不夠,需要擴(kuò)容
    fmt.Print(s2, a)  // 現(xiàn)在a不再是s2的底層數(shù)組了,這里會(huì)復(fù)制一份數(shù)組到一個(gè)新的數(shù)組,作為新的底層數(shù)組。a里的元素不會(huì)被覆蓋
}

這里在生成數(shù)組a的時(shí)候推導(dǎo)了數(shù)組的長(zhǎng)度,而長(zhǎng)度也是數(shù)組類型的一部分,用上面的方法也可以把長(zhǎng)度推導(dǎo)出來(lái)。

向AI問(wèn)一下細(xì)節(jié)

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

AI