溫馨提示×

溫馨提示×

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

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

學習筆記-數(shù)組和切片

發(fā)布時間:2020-09-19 16:59:30 來源:網(wǎng)絡 閱讀:332 作者:1350368559 欄目:編程語言

參考極客時間:https://time.geekbang.org/column/article/14106

1、數(shù)組(array)類型和切片(slice)類型 區(qū)別是什么?

數(shù)組類型的值(以下簡稱數(shù)組)的長度是固定的,而切片類型的值(以下簡稱切片)是可變長的。

數(shù)組的長度在聲明它的時候就必須給定,并且之后不會再改變??梢哉f,數(shù)組的長度是其類型的一部分。比如,[1]string和[2]string就是兩個不同的數(shù)組類型。而切片的類型字面量中只有元素的類型,而沒有長度。切片的長度可以自動地隨著其中元素數(shù)量的增長而增長,但不會隨著元素數(shù)量的減少而減小。
學習筆記-數(shù)組和切片

其實可以把切片看做是對數(shù)組的一層簡單的封裝,因為在每個切片的底層數(shù)據(jù)結構中,一定會包含一個數(shù)組。數(shù)組可以被叫做切片的底層數(shù)組,而切片也可以被看作是對數(shù)組的某個連續(xù)片段的引用。

Go 語言的切片類型屬于引用類型,同屬引用類型的還有字典類型、通道類型、函數(shù)類型等;而 Go 語言的數(shù)組類型則屬于值類型,同屬值類型的有基礎數(shù)據(jù)類型以及結構體類型。

Go 語言里不存在像 Java 等編程語言中令人困惑的“傳值或傳引用”問題。在 Go 語言中,我們判斷所謂的“傳值”或者“傳引用”只要看被傳遞的值的類型就好了。如果傳遞的值是引用類型的,那么就是“傳引用”。如果傳遞的值是值類型的,那么就是“傳值”。從傳遞成本的角度講,引用類型的值往往要比值類型的值低很多。

我們在數(shù)組和切片之上都可以應用索引表達式,得到的都會是某個元素。我們在它們之上也都可以應用切片表達式,也都會得到一個新的切片。

調用內建函數(shù)len,得到數(shù)組和切片的長度。通過調用內建函數(shù)cap,我們可以得到它們的容量。但要注意,數(shù)組的容量永遠等于其長度,都是不可變的。切片的容量卻不是這樣,并且它的變化是有規(guī)律可尋的。

2、怎樣正確估算切片的長度和容量?

 package main

import "fmt"

func main() {
    // 示例1。
    s1 := make([]int, 5)
    fmt.Printf("The length of s1: %d\n", len(s1))
    fmt.Printf("The capacity of s1: %d\n", cap(s1))
    fmt.Printf("The value of s1: %d\n", s1)
    s2 := make([]int, 5, 8)
    fmt.Printf("The length of s2: %d\n", len(s2))
    fmt.Printf("The capacity of s2: %d\n", cap(s2))
    fmt.Printf("The value of s2: %d\n", s2)
    fmt.Println()
  go run demo15.go 
The length of s1: 5
The capacity of s1: 5
The value of s1: [0 0 0 0 0]
The length of s2: 5
The capacity of s2: 8
The value of s2: [0 0 0 0 0]

我用內建函數(shù)make聲明了一個[]int類型的變量s1。我傳給make函數(shù)的第二個參數(shù)是5,從而指明了該切片的長度。我用幾乎同樣的方式聲明了切片s2,只不過多傳入了一個參數(shù)8以指明該切片的容量?,F(xiàn)在,具體的問題是:切片s1和s2的容量都是多少?

這道題的典型回答:切片s1和s2的容量分別是5和8。
s1的容量為什么是5呢?因為我在聲明s1的時候把它的長度設置成了5。當我們用make函數(shù)初始化切片時,如果不指明其容量,那么它就會和長度一致。如果在初始化時指明了容量,那么切片的實際容量也就是它了。這也正是s2的容量是8的原因。

過s2再來明確下長度、容量以及它們的關系。我在初始化s2代表的切片時,同時也指定了它的長度和容量。我在剛才說過,可以把切片看做是對數(shù)組的一層簡單的封裝,因為在每個切片的底層數(shù)據(jù)結構中,一定會包含一個數(shù)組。數(shù)組可以被叫做切片的底層數(shù)組,而切片也可以被看作是對數(shù)組的某個連續(xù)片段的引用。在這種情況下,切片的容量實際上代表了它的底層數(shù)組的長度,這里是8。(注意,切片的底層數(shù)組等同于我們前面講到的數(shù)組,其長度不可變。)

有一個窗口,你可以通過這個窗口看到一個數(shù)組,但是不一定能看到該數(shù)組中的所有元素,有時候只能看到連續(xù)的一部分元素。

這個數(shù)組就是切片s2的底層數(shù)組,而這個窗口就是切片s2本身。s2的長度實際上指明的就是這個窗口的寬度,決定了你透過s2,可以看到其底層數(shù)組中的哪幾個連續(xù)的元素。由于s2的長度是5,所以你可以看到底層數(shù)組中的第 1 個元素到第 5 個元素,對應的底層數(shù)組的索引范圍是 [0, 4]。切片代表的窗口也會被劃分成一個一個的小格子,就像我們家里的窗戶那樣。每個小格子都對應著其底層數(shù)組中的某一個元素。
s2為例,這個窗口最左邊的那個小格子對應的正好是其底層數(shù)組中的第一個元素,即索引為0的那個元素。因此可以說,s2中的索引從0到4所指向的元素恰恰就是其底層數(shù)組中索引從0到4代表的那 5 個元素。
我們用make函數(shù)或切片值字面量(比如[]int{1, 2, 3})初始化一個切片時,該窗口最左邊的那個小格子總是會對應其底層數(shù)組中的第 1 個元素。

再來看一個例子:

package main

import "fmt"

func main() {

    // 示例2。
    s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
    s4 := s3[3:6]
    fmt.Printf("The length of s4: %d\n", len(s4))
    fmt.Printf("The capacity of s4: %d\n", cap(s4))
    fmt.Printf("The value of s4: %d\n", s4)
    fmt.Println()
go run demo15.go 
The length of s4: 3
The capacity of s4: 5
The value of s4: [4 5 6]

學習筆記-數(shù)組和切片

切片s3中有 8 個元素,分別是從1到8的整數(shù)。s3的長度和容量都是8。然后,我用切片表達式s3[3:6]初始化了切片s4。問題是,這個s4的長度和容量分別是多少?

切片表達式中的方括號里的那兩個整數(shù)[3:6]都代表什么?

[3:6]要表達的就是透過新窗口能看到的s3中元素的索引范圍是從3到5(注意,不包括6)。這里的3可被稱為起始索引,6可被稱為結束索引。那么s4的長度就是6減去3,即3。因此可以說,s4中的索引從0到2指向的元素對應的是s3及其底層數(shù)組中索引從3到5的那 3 個元素。

再來看容量。我在前面說過,切片的容量代表了它的底層數(shù)組的長度,但這僅限于使用make函數(shù)或者切片值字面量初始化切片的情況。

更通用的規(guī)則是:一個切片的容量可以被看作是透過這個窗口最多可以看到的底層數(shù)組中元素的個數(shù)。

由于s4是通過在s3上施加切片操作得來的,所以s3的底層數(shù)組就是s4的底層數(shù)組。又因為,在底層數(shù)組不變的情況下,切片代表的窗口可以向右擴展,直至其底層數(shù)組的末尾。所以,s4的容量就是其底層數(shù)組的長度8, 減去上述切片表達式中的那個起始索引3,即5。切片代表的窗口是無法向左擴展的。也就是說,我們永遠無法透過s4看到s3中最左邊的那 3 個元素。

順便提一下把切片的窗口向右擴展到最大的方法。對于s4來說,切片表達式s4[0:cap(s4)]就可以做到。我想你應該能看懂。該表達式的結果值(即一個新的切片)會是[]int{4, 5, 6, 7, 8},其長度和容量都是5。

3、怎樣估算切片容量的增長?

一旦一個切片無法容納更多的元素,Go 語言就會想辦法擴容。但它并不會改變原來的切片,而是會生成一個容量更大的切片,然后將把原有的元素和新元素一并拷貝到新切片中。在一般的情況下,你可以簡單地認為新切片的容量(以下簡稱新容量)將會是原切片容量(以下簡稱原容量)的 2 倍。

但是,當原切片的長度(以下簡稱原長度)大于或等于1024時,Go 語言將會以原容量的1.25倍作為新容量的基準(以下新容量基準)。新容量基準會被調整(不斷地與1.25相乘),直到結果不小于原長度與要追加的元素數(shù)量之和(以下簡稱新長度)。最終,新容量往往會比新長度大一些,當然,相等也是可能的。

另外,如果我們一次追加的元素過多,以至于使新長度比原容量的 2 倍還要大,那么新容量就會以新長度為基準。注意,與前面那種情況一樣,最終的新容量在很多時候都要比新容量基準更大一些。更多細節(jié)可參見runtime包中 slice.go 文件里的growslice及相關函數(shù)的具體實現(xiàn)。

package main

import "fmt"

func main() {
    // 示例1。
    s6 := make([]int, 0)
    fmt.Printf("The capacity of s6: %d\n", cap(s6))
    for i := 1; i <= 5; i++ {
        s6 = append(s6, i)
        fmt.Printf("s6(%d): len: %d, cap: %d\n", i, len(s6), cap(s6))
    }
    fmt.Println()

    // 示例2。
    s7 := make([]int, 1024)
    fmt.Printf("The capacity of s7: %d\n", cap(s7))
    s7e1 := append(s7, make([]int, 200)...)
    fmt.Printf("s7e1: len: %d, cap: %d\n", len(s7e1), cap(s7e1))
    s7e2 := append(s7, make([]int, 400)...)
    fmt.Printf("s7e2: len: %d, cap: %d\n", len(s7e2), cap(s7e2))
    s7e3 := append(s7, make([]int, 600)...)
    fmt.Printf("s7e3: len: %d, cap: %d\n", len(s7e3), cap(s7e3))
    fmt.Println()

    // 示例3。
    s8 := make([]int, 10)
    fmt.Printf("The capacity of s8: %d\n", cap(s8))
    s8a := append(s8, make([]int, 11)...)
    fmt.Printf("s8a: len: %d, cap: %d\n", len(s8a), cap(s8a))
    s8b := append(s8a, make([]int, 23)...)
    fmt.Printf("s8b: len: %d, cap: %d\n", len(s8b), cap(s8b))
    s8c := append(s8b, make([]int, 45)...)
    fmt.Printf("s8c: len: %d, cap: %d\n", len(s8c), cap(s8c))
}
go run demo16.go 
The capacity of s6: 0
s6(1): len: 1, cap: 1
s6(2): len: 2, cap: 2
s6(3): len: 3, cap: 4
s6(4): len: 4, cap: 4
s6(5): len: 5, cap: 8

The capacity of s7: 1024
s7e1: len: 1224, cap: 1280
s7e2: len: 1424, cap: 1696
s7e3: len: 1624, cap: 2048

The capacity of s8: 10
s8a: len: 21, cap: 22
s8b: len: 44, cap: 44
s8c: len: 89, cap: 96

4、切片的底層數(shù)組什么時候會被替換?

確切地說,一個切片的底層數(shù)組永遠不會被替換。為什么?雖然在擴容的時候 Go 語言一定會生成新的底層數(shù)組,但是它也同時生成了新的切片。

它只是把新的切片作為了新底層數(shù)組的窗口,而沒有對原切片,及其底層數(shù)組做任何改動。

請記住,在無需擴容時,append函數(shù)返回的是指向原底層數(shù)組的新切片,而在需要擴容時,append函數(shù)返回的是指向新底層數(shù)組的新切片。所以,嚴格來講,“擴容”這個詞用在這里雖然形象但并不合適。不過鑒于這種稱呼已經(jīng)用得很廣泛了,我們也沒必要另找新詞了。

順便說一下,只要新長度不會超過切片的原容量,那么使用append函數(shù)對其追加元素的時候就不會引起擴容。這只會使緊鄰切片窗口右邊的(底層數(shù)組中的)元素被新的元素替換掉。你可以運行 demo17.go 文件以增強對這些知識的理解。

package main

import "fmt"

func main() {
    // 示例1。
    a1 := [7]int{1, 2, 3, 4, 5, 6, 7}
    fmt.Printf("a1: %v (len: %d, cap: %d)\n",
        a1, len(a1), cap(a1))
    s9 := a1[1:4]
    //s9[0] = 1
    fmt.Printf("s9: %v (len: %d, cap: %d)\n",
        s9, len(s9), cap(s9))
    for i := 1; i <= 5; i++ {
        s9 = append(s9, i)
        fmt.Printf("s9(%d): %v (len: %d, cap: %d)\n",
            i, s9, len(s9), cap(s9))
    }
    fmt.Printf("a1: %v (len: %d, cap: %d)\n",
        a1, len(a1), cap(a1))
    fmt.Println()

}
go run demo17.go 
a1: [1 2 3 4 5 6 7] (len: 7, cap: 7)
s9: [2 3 4] (len: 3, cap: 6)
s9(1): [2 3 4 1] (len: 4, cap: 6)
s9(2): [2 3 4 1 2] (len: 5, cap: 6)
s9(3): [2 3 4 1 2 3] (len: 6, cap: 6)
s9(4): [2 3 4 1 2 3 4] (len: 7, cap: 12)
s9(5): [2 3 4 1 2 3 4 5] (len: 8, cap: 12)
a1: [1 2 3 4 1 2 3] (len: 7, cap: 7)

slice中的三個點號...

package main

import "fmt"

type Product struct {
    ID    int64   `json:"id"`
    Name  string  `json:"name"`
    Info  string  `json:"info"`
    Price float64 `json:"price"`
}

var products []Product

func initProducts() {
    product1 := Product{ID: 1, Name: "Chicha Morada", Info: "Chicha  level (wiki)", Price: 7.99}
    product2 := Product{ID: 2, Name: "Chicha de jora", Info: "Chicha de sedays (wiki)", Price: 5.95}
    product3 := Product{ID: 3, Name: "Pisco1", Info: "Pisco is a emakile (wiki)", Price: 9.95}
    product4 := Product{ID: 4, Name: "Pisco2", Info: "Pisco is a emakile (wiki)", Price: 9.95}
    product5 := Product{ID: 5, Name: "Pisco3", Info: "Pisco is a emakile (wiki)", Price: 9.95}
    products = append(products, product1, product2, product3, product4, product5)
    // fmt.Println(products)
}

func main() {
    initProducts()
    //products = append(products[:i],products[i+1:])
    //如果沒有省略號,如下,會提示:
    //C./test.go:37:19: cannot use products[i + 1:] (type []Product) as type Product in append

    //正確用法
    //i=1,products[:i]表示id=1的products[0],值是:product1,
    //i=1,products[i+1:]...就是products[2:]...,表示products[2:],值是product3,product4,product5
    i := 1
    products = append(products[:i], products[i+1:]...)
    fmt.Println(products)
    //打印product1,product3,product4,product5
}
go run test.go 
[{1 Chicha Morada Chicha  level (wiki) 7.99} {3 Pisco1 Pisco is a emakile (wiki) 9.95} {4 Pisco2 Pisco is a emakile (wiki) 9.95} {5 Pisco3 Pisco is a emakile (wiki) 9.95}]
向AI問一下細節(jié)

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

AI