Go基礎(chǔ)——Slice切片

修改時間 修改內(nèi)容
2019-04-02 切片大小和切片容量和擴(kuò)容的解釋
2019-04-05 go中值類型和引用類型
2019-08-07 切片清空數(shù)據(jù)

概述

1.切片是引用類型,數(shù)組和切片有著緊密的關(guān)聯(lián),slice的底層是引用一個數(shù)組對象,可以理解為切片是對數(shù)組的封裝
2一個slice由三個部分構(gòu)成:指針、長度和容量。指針指向第一個slice元素對應(yīng)的底層數(shù)組元素的地址。
3切片的長度是變化的,而數(shù)組的長度是固定不變的。
4多個不同slice之間可以共享底層的數(shù)據(jù)
5 slice 源碼地址在/runtime/slice.go

值類型和引用類型
類型 范圍
引用類型 切片,字典,通道,函數(shù)
值類型 數(shù)組,基本數(shù)據(jù)類型,結(jié)構(gòu)體

聲明Slice

帶有 T 類型元素的切片由 []T 表示,其中T代表slice中元素的類型。切片在內(nèi)部可由一個結(jié)構(gòu)體類型表示,形式如下:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

可見一個slice由三個部分構(gòu)成:指針、長度和容量。指針指向第一個slice元素對應(yīng)的底層數(shù)組元素的地址。長度對應(yīng)slice中元素的數(shù)目;長度不能超過容量,容量一般是從slice的開始位置到底層數(shù)組的結(jié)尾位置。通過len和cap函數(shù)分別返回slice的長度和容量。

創(chuàng)建Slice

1直接聲明創(chuàng)建 slice

 []<元素類型>{元素1, 元素2, …}

創(chuàng)建一個有 3 個整型元素的數(shù)組,并返回一個存儲在 c 中的切片引用。

    c := []int{6, 7, 8} 

make() 函數(shù)創(chuàng)建 slice

    s1 := make([]int, 5)  //長度和容量都是 5
    s2 := make([]int, 3, 10)  //長度是3,容量是10
    fmt.Println(cap(s1),s2)

基于底層數(shù)組數(shù)組或切片創(chuàng)建

基于現(xiàn)有的切片或者數(shù)組創(chuàng)建,使用[i:j]這樣的操作符即可,她表示以i索引開始,到j(luò)索引結(jié)束,截取原數(shù)組或者切片,創(chuàng)建而成的新切片,新切片的值包含原切片的i索引,但是不包含j索引。注意i和j都不能超過原切片或者數(shù)組的索引

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

新的切片和原數(shù)組或原切片共用的是一個底層數(shù)組,所以當(dāng)修改的時候,底層數(shù)組的值就會被改變,所以原切片的值也改變了。

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

切片與數(shù)組的區(qū)別

1.切片不是數(shù)組,但是切片底層指向數(shù)組
2.切片本身長度是不一定的因此不可以比較,數(shù)組是可以的。
3.切片是變長數(shù)組的替代方案,可以關(guān)聯(lián)到指向的底層數(shù)組的局部或者全部。
4.切片是引用傳遞(傳遞指針地址),而數(shù)組是值傳遞(拷貝值)
5.切片可以直接創(chuàng)建,引用其他切片或數(shù)組創(chuàng)建
6.如果多個切片指向相同的底層數(shù)組,其中一個值的修改會影響所有的切片


圖片來源:極客時間的Go語言核心36講

切片的修改

切片自己不擁有任何數(shù)據(jù)。它只是底層數(shù)組的一種表示。對切片所做的任何修改都會反映在底層數(shù)組中。

package main

import (
    "fmt"
)

func main() {
    arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    slice := arr[2:5]
    fmt.Println("array before", arr)
    for i := range slice {
        slice[i]++
    }
    fmt.Println("array after ", arr)
}

在上述程序的第 9 行,我們根據(jù)數(shù)組索引 2,3,4 創(chuàng)建一個切片 dslice。for 循環(huán)將這些索引中的值逐個遞增。當(dāng)我們使用 for 循環(huán)打印數(shù)組時,我們可以看到對切片的更改反映在數(shù)組中。該程序的輸出是

array before [0 1 2 3 4 5 6 7 8 9]
array after    [0 1 3 4 5 5 6 7 8 9]

當(dāng)多個切片共用相同的底層數(shù)組時,每個切片所做的更改將反映在數(shù)組中。

package main

import (
    "fmt"
)

func main() {
    array := [4]int{10, 20 ,30, 40}
    slice1 := array[:]
    slice2 := array[:]
    fmt.Println("array before change:", array)
    slice1[0] = 60
    fmt.Println("array after modification to slice slice1:", array)
    slice2[1] = 70
    fmt.Println("array after modification to slice slice2:", array)
}

在 9 行中,numa [:] 缺少開始和結(jié)束值。開始和結(jié)束的默認(rèn)值分別為 0len (numa)。兩個切片 nums1nums2 共享相同的數(shù)組。該程序的輸出是

array before change: [10 20 30 40]
array after modification to slice slice1: [60 20 30 40]
array after modification to slice slice2: [60 70 30 40]

從輸出中可以清楚地看出,當(dāng)切片共享同一個數(shù)組時,每個所做的修改都會反映在數(shù)組中。

切片的len(長度)和cap(容量)

s := make([]int, 5)當(dāng)使用make函數(shù)初始化切片的是,如果不指定切片的容量,那么切片的長度就是切片的容量。

func TestSlice(t *testing.T) {
    a := []int{1, 2, 3, 4}
    b := a[:2]
    c := a[2:]
    t.Log(a, len(a), cap(a))
    t.Log(b, len(b), cap(b))
    t.Log(c, len(c), cap(c))
    t.Log("append data")
    b = append(b, 5)
    t.Log(a, len(a), cap(a))
    t.Log(b, len(b), cap(b))
    t.Log(c, len(c), cap(c))
}

執(zhí)行結(jié)果是:

 [1 2 3 4] 4 4
 [1 2] 2 4
 [3 4] 2 2
append data
[1 2 5 4] 4 4
[1 2 5] 3 4
[5 4] 2 2

切片的長度是切片中的元素數(shù)。
切片的容量是從創(chuàng)建切片索引開始的底層數(shù)組中元素數(shù)。

func main() {
    var x []int
    c := 0
    for i := 0; i < 100000; i++ {
        x = append(x, i)
        if c!= cap(x){
            c =cap(x)
            fmt.Printf("index=%d cap=%d\n", i+1, c)
        }
    }
}

執(zhí)行的結(jié)果

index=1 cap=1
index=2 cap=2
index=3 cap=4
index=5 cap=8
index=9 cap=16
index=17 cap=32
index=33 cap=64
index=65 cap=128
index=129 cap=256
index=257 cap=512
index=513 cap=1024
index=1025 cap=1280
index=1281 cap=1696
index=1697 cap=2304
index=2305 cap=3072
index=3073 cap=4096
index=4097 cap=5120
index=5121 cap=7168
index=7169 cap=9216
index=9217 cap=12288
index=12289 cap=15360
index=15361 cap=19456
index=19457 cap=24576
index=24577 cap=30720
index=30721 cap=38912
index=38913 cap=49152
index=49153 cap=61440
index=61441 cap=76800
index=76801 cap=96256
index=96257 cap=120832

切片增加元素

使用 append 函數(shù)可以將新元素追加到切片上。append 函數(shù)的定義是

func append(s[]T,x ... T)[]T

append可以直接在切片尾部追加元素

package main

import (
    "fmt"
)

func main() {
    str := []string{"a", "b", "c"}
    fmt.Println("strs:", str, " length:", len(str), "capacity:", cap(str))
    str = append(str, "d")
    fmt.Println("strs:", str, " length:", len(str), " capacity:", cap(str))
}

在上述程序中,str 的容量最初是 3。在第 10 行,我們給 str 添加了一個新的元素,并把 append(str, "d") 返回的切片賦值給 str?,F(xiàn)在 str 的容量翻了一番,變成了 6。

strs: [a b c]  length: 3 capacity: 3
strs: [a b c d]  length: 4  capacity: 6
func TestSliceCap(t *testing.T) {
    a := []int{0}
    a = append(a, 0)
    b := a[:]
    a = append(a, 2)
    b = append(b, 1)
    t.Log(a[2])
    t.Log(b[2])
    t.Log(a)
    t.Log(b)

    // 一樣的代碼,只是以一個稍大的 slice 開始
    c := []int{0, 0}
    c = append(c, 0)
    d := c[:]
    c = append(c, 2)
    d = append(d, 1)
    t.Log(c[3])
    t.Log(d[3])
    t.Log(c)
    t.Log(d)
}

當(dāng)切片新長度沒有超過切片的容量的時,使用append函數(shù)追加數(shù)據(jù)不會創(chuàng)建新的底層數(shù)組,但是會造成新增加的數(shù)據(jù)覆蓋原來底層索引位置的數(shù)據(jù),打印結(jié)果:

2
1
[0 0 2]
[0 0 1]
1
1
[0 0 0 1]
[0 0 0 1]

切片的nil和空值

nil slice表示數(shù)組不存在,empty slice表示集合為空。對slice為空的判斷建議len函數(shù)

var s []int         //nil值
    var t = []int{}     //空值
    a,_:= json.Marshal(s)
    fmt.Println(string(a))
    b,_:=json.Marshal(t)
    fmt.Println(string(b))

分別對nil和空slice做json序列化是不同的, nil slice會變成null,empty是[] 需要注意

null
[]

切片添加切片

使用 ... 運算符將一個切片添加到另一個切片。

package main

import (
    "fmt"
)

func main() {
    veggies := []string{"potatoes", "tomatoes", "brinjal"}
    fruits := []string{"oranges", "apples"}
    food := append(veggies, fruits...)
    fmt.Println("food:",food)
}

在上述程序的第 10 行,food 是通過 append(veggies, fruits...) 創(chuàng)建。程序的輸出為 food: [potatoes tomatoes brinjal oranges apples]。
特別需要注意的是如果新切片的長度未超過源切片的容量,則返回源切片,如果追加后的新切片長度超過源切片的容量,則會返回全新的切片。

func main() {
    s1 := []int{1,2,3,4,5}
    fmt.Printf("s1:%p %d %d %v\n",s1,len(s1),cap(s1),s1)
    s2 :=append(s1,6)
    fmt.Printf("s3:%p %d %d %v\n",s2,len(s2),cap(s2),s2)

    s3 := s1[0:4]
    fmt.Printf("s3:%p %d %d %v\n",s3,len(s3),cap(s3),s3)
    s4 := append(s3,6)
    fmt.Printf("s4:%p %d %d %v\n",s4,len(s4),cap(s4),s4)
    fmt.Printf("s1:%p %d %d %v\n",s1,len(s1),cap(s1),s1)
    s5 := append(s4,8)
    fmt.Printf("s5:%p %d %d %v\n",s5,len(s5),cap(s5),s5)
}

切片的函數(shù)傳遞

切片包含長度、容量和指向數(shù)組第零個元素的指針。當(dāng)切片傳遞給函數(shù)時,即使它通過值傳遞,指針變量也將引用相同的底層數(shù)組。因此,當(dāng)切片作為參數(shù)傳遞給函數(shù)時,函數(shù)內(nèi)所做的更改也會在函數(shù)外可見。

package main

import (
    "fmt"
)

func subtactOne(numbers []int) {
    for i := range numbers {
        numbers[i] -= 2
    }
}
func main() {
    nos := []int{8, 7, 6}
    fmt.Println("slice before function call", nos)
    subtactOne(nos)                               // function modifies the slice
    fmt.Println("slice after function call", nos) // modifications are visible outside
}

上述程序的行號 17 中,調(diào)用函數(shù)將切片中的每個元素遞減 2。在函數(shù)調(diào)用后打印切片時,這些更改是可見的。如果你還記得,這是不同于數(shù)組的,對于函數(shù)中一個數(shù)組的變化在函數(shù)外是不可見的。

array before function call [8 7 6]  
array after function call [6 5 4]

多維切片

類似于數(shù)組,切片可以有多個維度。

package main

import (
    "fmt"
)

func main() {  
     pls := [][]string {
            {"C", "C++"},
            {"JavaScript"},
            {"Go", "Rust"},
            }
    for _, v1 := range pls {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
}

程序的輸出為,

C C++  
JavaScript  
Go Rust

copy

切片持有對底層數(shù)組的引用。只要切片在內(nèi)存中,數(shù)組就不能被垃圾回收。在內(nèi)存管理方面,這是需要注意的。讓我們假設(shè)我們有一個非常大的數(shù)組,我們只想處理它的一小部分。然后,我們由這個數(shù)組創(chuàng)建一個切片,并開始處理切片。這里需要重點注意的是,在切片引用時數(shù)組仍然存在內(nèi)存中。
一種解決方法是使用copy 函數(shù) 來生成一個切片的副本。這樣我們可以使用新的切片,原始數(shù)組可以被垃圾回收。

`func copy(dst,src[]T)int` 
package main

import (
    "fmt"
)

func main() {
    s1 :=[]int{1,2,3,4,5}
    fmt.Println("s1",s1)
    s2 := make([]int,len(s1))
    fmt.Println("s2",s2)
    copy(s2,s1)
    fmt.Println("s2",s2)

    s3 :=make([]int,len(s1)-2)
    copy(s3,s1);
    fmt.Println("s3",s3)
    s4 :=make([]int,len(s1)-1)
    copy(s4[1:3],s1[2:4]);
    fmt.Println("s4",s4)
}

打印結(jié)果:

s1 [1 2 3 4 5]
s2 [0 0 0 0 0]
s2 [1 2 3 4 5]
s3 [1 2 3]
s4 [0 3 4 0]

清空數(shù)據(jù)

how-do-you-clear-a-slice-in-go(https://stackoverflow.com/questions/16971741/how-do-you-clear-a-slice-in-go)

package main

import (
    "fmt"
)

func dump(letters []string) {
    fmt.Println("letters = ", letters)
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    for i := range letters {
        fmt.Println(i, letters[i])
    }
}

func main() {
    letters := []string{"a", "b", "c", "d"}
    dump(letters)
    // clear the slice
    letters = nil
    dump(letters)
    // add stuff back to it
    letters = append(letters, "e")
    dump(letters)
}

輸出結(jié)果

letters =  [a b c d]
4
4
0 a
1 b
2 c
3 d
letters =  []
0
0
letters =  [e]
1
1
0 e
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容