Go數(shù)組、切片

1 數(shù)組

1.1 如何定義數(shù)組

數(shù)組是具有相同類型且長(zhǎng)度固定的一組連續(xù)數(shù)據(jù)。在go語言中我們可以使用如下幾種方式來定義數(shù)組。

//方式一
var arr1 = [5]int{}
//方式二
var arr2 = [5]int{1,2,3,4,5}
//方式三
var arr3 = [5]int{3:10}

輸出以上三個(gè)變量的值如下所示:

arr1 [0 0 0 0 0]
arr2 [1 2 3 4 5]
arr3 [0 0 0 10 0]
  • 方法一在聲明時(shí)沒有為其指定初值,所以數(shù)組內(nèi)的值被初始化為類型的零值。
  • 方法二使用顯示的方式為數(shù)組定義初值。
  • 方法三通過下標(biāo)的方式為下標(biāo)為3的位置賦上了初值10

而且在數(shù)組的定義是包含其長(zhǎng)度的,也就是說[3]int與[4]int是兩種不同的數(shù)據(jù)類型。

1.2 如何操作數(shù)據(jù)

在上面的arr1我們沒有為其指定初值,那么之后我們可以通過循環(huán)的方式為其賦予初值

for i := 0; i < len(arr1); i++ {
        arr1[i] = i * 10
    }

此時(shí)我們?cè)佥敵鲆幌聰?shù)組arr1,可以看到其值已經(jīng)變化

arr1 update:  [0 10 20 30 40]

我們還可以通過循環(huán)的方式遍歷數(shù)組,與上面不同的是,for循環(huán)提供這樣的方式來為便利數(shù)組

for index, value := range arr1 {
        fmt.Printf("index: %d, value: %d\n", index, value)
    }

通過range來遍歷數(shù)組會(huì)有兩個(gè)返回值,其中第一個(gè)為數(shù)組的索引,第二個(gè)位置為對(duì)應(yīng)的值,輸出結(jié)果如下:

index: 0, value: 0
index: 1, value: 10
index: 2, value: 20
index: 3, value: 30
index: 4, value: 40

當(dāng)然不想要索引值也是可以的,可以用_代替。

1.3 多維數(shù)組

與其他語言一樣,go語言也可以定義多維數(shù)組,可以選擇的定義方式如下:

var arr4 = [5][5]int{
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
    }

輸出當(dāng)前數(shù)組,可以看到數(shù)組內(nèi)的值如下所示

[[1 2 3 4 5] [6 7 8 9 10] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

1.4 數(shù)組作為函數(shù)參數(shù)

go語言在傳遞數(shù)組時(shí)會(huì)對(duì)其進(jìn)行拷貝,所以如果傳遞的是大數(shù)組的話會(huì)非常占內(nèi)存,所以一般情況下很少直接傳遞一個(gè)數(shù)組,避免這種情況我們可以使用以下兩種方式:

  • 傳遞數(shù)組的指針
  • 傳遞切片(具體內(nèi)容見下一小節(jié))

1.5 指針數(shù)組與數(shù)組指針

對(duì)于指針數(shù)組和數(shù)組指針在c或c++中也經(jīng)常被討論,尤其對(duì)于初學(xué)者來說會(huì)分辨不清楚。其實(shí)在每個(gè)詞中間添加一個(gè)“的“就很好理解了,指針的數(shù)組,數(shù)組的指針。

1.指針數(shù)組

對(duì)于指針數(shù)組來說,就是:一個(gè)數(shù)組里面裝的都是指針,在go語言中數(shù)組默認(rèn)是值傳遞的,所以如果我們?cè)诤瘮?shù)中修改傳遞過來的數(shù)組對(duì)原來的數(shù)組是沒有影響的。

func main() {
    var a [5]int
    fmt.Println(a)
    test(a)
    fmt.Println(a)
}

func test(a [5]int) {
    a[1] = 2
    fmt.Println(a)
}

輸出

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

但是如果我們一個(gè)函數(shù)傳遞的是指針數(shù)組,情況會(huì)有什么不一樣呢?

func main() {
    var a [5]*int
    fmt.Println(a)
    for i := 0; i < 5; i++ {
        temp := i
        a[i] = &temp
    }
    for i := 0; i < 5; i++ {
        fmt.Print(" ", *a[i])
    }
    fmt.Println()
    test1(a)
    for i := 0; i < 5; i++ {
        fmt.Print(" ", *a[i])
    }
}

func test1(a [5]*int) {
    *a[1] = 2
    for i := 0; i < 5; i++ {
        fmt.Print(" ", *a[i])
    }
    fmt.Println()
}

我們先來看一下程序的運(yùn)行結(jié)果

[<nil> <nil> <nil> <nil> <nil>]
 0 1 2 3 4
 0 2 2 3 4
 0 2 2 3 4

可以看到初始化值全是nil,也就驗(yàn)證了指針數(shù)組內(nèi)部全都是一個(gè)一個(gè)指針,之后我們將其初始化,內(nèi)部的每個(gè)指針指向一塊內(nèi)存空間。

注:在初始化的時(shí)候如果直接另a[i] = &i那么指針數(shù)組內(nèi)部存儲(chǔ)的全是同一個(gè)地址,所以輸出結(jié)果也一定是相同的

然后我們將這個(gè)指針數(shù)組傳遞給test1函數(shù),對(duì)于數(shù)組的參數(shù)傳遞仍然是復(fù)制的形式也就是值傳遞,但是因?yàn)閿?shù)組中每個(gè)元素是一個(gè)指針,所以test1函數(shù)復(fù)制的新數(shù)組中的值仍然是這些指針指向的具體地址值,這時(shí)改變a[1]這塊存儲(chǔ)空間地址指向的值,那么原實(shí)參指向的值也會(huì)變?yōu)?,具體流程如下圖所示。

2.數(shù)組指針

了解了指針數(shù)組之后,再來看一下數(shù)組指針,數(shù)組指針的全稱應(yīng)該叫做,指向數(shù)組的指針,在go語言中我們可以如下操作。

func main() {
    var a [5]int
    var aPtr *[5]int
    aPtr = &a
    //這樣簡(jiǎn)短定義也可以aPtr := &a
    fmt.Println(aPtr)
    test(aPtr)
    fmt.Println(aPtr)
}

func test(aPtr *[5]int) {
    aPtr[1] = 5
    fmt.Println(aPtr)
}

我們先定義了一個(gè)數(shù)組a,然后定一個(gè)指向數(shù)組a的指針aPtr,然后將這個(gè)指針傳入一個(gè)函數(shù),在函數(shù)中我們改變了具體的值,程序的輸出結(jié)果

&[0 0 0 0 0]
&[0 5 0 0 0]
&[0 5 0 0 0]

通過上面的圖我們可以看見雖然main和test函數(shù)中的aPtr是不同的指針,但是他們都指向同一塊數(shù)組的內(nèi)存空間,所以無論在main函數(shù)還是在test函數(shù)中對(duì)數(shù)組的操作都會(huì)直接改變數(shù)組。

2 切片

因?yàn)閿?shù)組是固定長(zhǎng)度,所以在一些場(chǎng)合下就顯得不夠靈活,所以go語言提供了一種更為便捷的數(shù)據(jù)類型叫做切片。切片操作與數(shù)據(jù)類似,但是它的長(zhǎng)度是不固定的,可以追加元素,如果以達(dá)到當(dāng)前切片容量的上限會(huì)再自動(dòng)擴(kuò)容。

2.1 如何定義切片

可以通過以下幾種方式定義切片

package main

import "fmt"

func main() {
    //方法一:聲明了一個(gè)空切片
    var s1 = []int{}
    //方法二:聲明了一個(gè)長(zhǎng)度為3的切片
    var s2 = []int{1, 2, 3}
    //方法三:聲明了一個(gè)長(zhǎng)度為5的空切片
    var s3 = make([]int, 5)
    //方法四:聲明了一個(gè)長(zhǎng)度為5容量為10的切片
    var s4 = make([]int, 5, 10)

    //可以看到切片的定義與數(shù)組類似,但是定義切片不需要為其指定長(zhǎng)度。

    //我們可以通過len()和cap()這兩個(gè)函數(shù)來獲取切片的長(zhǎng)度和容量,下面就來依次看下上面各切片的長(zhǎng)度以及容量。
    fmt.Println("s1", s1, len(s1), cap(s1))
    fmt.Println("s2", s2, len(s2), cap(s2))
    fmt.Println("s3", s3, len(s3), cap(s3))
    fmt.Println("s4", s4, len(s4), cap(s4))

    //如果我們通過這種方式定義一個(gè)切片,那么他會(huì)被賦予切片的空值nil。
    var s5 []int
    fmt.Println("s5", s5, len(s5), cap(s5))

}

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

s1 [] 0 0
s2 [1 2 3] 3 3
s3 [0 0 0 0 0] 5 5
s4 [0 0 0 0 0] 5 10
s5 [] 0 0

切片操作
我們可以通過如下的方式在數(shù)組和切片上繼續(xù)獲取切片

package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    s := []int{6, 7, 8, 9, 10}

    s1 := arr[2:4] //起始位0,從第2位到第4位前
    s2 := arr[:3]  //從起始位0,到第3位前
    s3 := arr[2:]  //從第2位到終了位
    s4 := s[1:3]   //從第1位到第3位前

    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)
    fmt.Println("s3:", s3)
    fmt.Println("s4:", s4)
}

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

s1: [3 4]
s2: [1 2 3]
s3: [3 4 5]
s4: [7 8]

可以看到切片操作是“包左不包右”,例如arr[2:4]是選擇arr數(shù)組內(nèi)下標(biāo)為2,3的兩個(gè)元素。如果:左邊沒有起始位置則默認(rèn)從頭開始,同理如果右邊沒有終止位置則默認(rèn)到末尾。

2.2 切片的擴(kuò)充與拼接

我們之前介紹了切片的定義以及一些切片的操作,下面就來看一下如何對(duì)切片進(jìn)行擴(kuò)充。

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := a[1:3]

    b = append(b, 4)
    b = append(b, 5)
    b = append(b, 6)
    b = append(b, 7)

    fmt.Println(a)
    fmt.Println(b)
}

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

[1 2 3]
[2 3 4 5 6 7]

如果想要將兩個(gè)切片進(jìn)行拼接可以使用如下這種方式:

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := a[1:3]

    fmt.Println(b)

    a = append(a, b...)//兩個(gè)切片,關(guān)鍵...

    fmt.Println(a)
}

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

[2 3]
[1 2 3 2 3]

如果我們想要將一個(gè)切片的值復(fù)制給另一個(gè)切片,go語言也提供了非常簡(jiǎn)便的方式。

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := make([]int, 6)

    fmt.Println(a)
    fmt.Println(b)

    copy(b, a) //copy(拷貝先,拷貝元)

    fmt.Println(a)
    fmt.Println(b)
}

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

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

你不妨動(dòng)手試一試下面幾個(gè)操作結(jié)果會(huì)怎么樣:

聲明b切片時(shí),其長(zhǎng)度比a切片長(zhǎng),復(fù)制結(jié)果是怎么樣的?
聲明b切片時(shí),其長(zhǎng)度比a切片短,復(fù)制結(jié)果是怎么樣的?
聲明b切片時(shí),其長(zhǎng)度被定義為0,那么調(diào)用copy函數(shù)會(huì)報(bào)錯(cuò)嗎?

package main

import "fmt"

func main() {
    a := []int{1, 2, 3}
    b := make([]int, 6)
    c := make([]int, 2)
    d := make([]int, 0)

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)

    copy(b, a) //copy(拷貝先,拷貝元) 長(zhǎng)
    copy(c, a) //copy(拷貝先,拷貝元) 短
    copy(d, a) //copy(拷貝先,拷貝元) 0

    fmt.Println("a:", a)
    fmt.Println("b:", b)
    fmt.Println("c:", c)
    fmt.Println("d:", d)
}

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

[1 2 3]
[0 0 0 0 0 0]
[0 0]
[]
a: [1 2 3]
b: [1 2 3 0 0 0]
c: [1 2]
d: []

結(jié)合append與copy函數(shù)可以實(shí)現(xiàn)很多非常實(shí)用的功能,你也可以動(dòng)手嘗試一下??
刪除處于索引i的元素
在索引i的位置插入元素
在索引i的位置插入長(zhǎng)度為 j 的新切片

package main

import "fmt"

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
    i := 1
    //刪除處于索引i的元素
    fmt.Println("刪除前:", a)
    b := a[:i]
    c := a[i+1:]

    fmt.Println(append(b, c...))
    fmt.Println("刪除后:", a)

    //在索引i的位置插入元素
    aa := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
    fmt.Println("插入元素前:", aa)
    d := aa[:i] //1
    e := aa[i:] //2,3,4,5,6,7,8,9,0

    ch := make([]int, len(d)+1) //
    copy(ch, d)
    ch[i] = 10
    f := append(ch, e...)
    fmt.Println("插入元素后:", f)

    //在索引i的位置插入長(zhǎng)度為j的新切片
    aaa := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
    ins := []int{11, 22}
    fmt.Println("插入元素前:", aaa)
    dd := aaa[:i] //1
    ee := aaa[i:] //2,3,4,5,6,7,8,9,0

    chh := make([]int, len(dd))
    copy(chh, dd)
    chhh := append(chh, ins...)

    ff := append(chhh, ee...)
    fmt.Println("插入元素后:", ff)
}

等等

刪除前: [1 2 3 4 5 6 7 8 9 0]
[1 3 4 5 6 7 8 9 0]
刪除后: [1 3 4 5 6 7 8 9 0 0]
插入元素前: [1 2 3 4 5 6 7 8 9 0]
插入元素后: [1 10 2 3 4 5 6 7 8 9 0]
插入元素前: [1 2 3 4 5 6 7 8 9 0]
插入元素后: [1 11 22 2 3 4 5 6 7 8 9 0]

2.3 切片與數(shù)組的關(guān)系

對(duì)于任何一個(gè)切片來說,其都有一個(gè)底層數(shù)組與之對(duì)應(yīng),我們可以將切片看作是一個(gè)窗口,透過這個(gè)窗口可以看到底層數(shù)組的一部分元素,對(duì)應(yīng)關(guān)系如下圖所示。


微信圖片_20201218234711.png

下面有幾個(gè)問題歡迎你自己動(dòng)手嘗試一下:

編寫程序看看切片的容量與數(shù)組的大小有什么關(guān)系呢?

如果我們?cè)谇衅显僮銮衅敲此麄儠?huì)指向相同的底層數(shù)組嗎?修改其中一個(gè)切片會(huì)影響其他切片的值么?其中一個(gè)切片擴(kuò)容到容量大小之后會(huì)更換底層數(shù)組,那么之前的其他切片也會(huì)指向新的底層數(shù)組嗎?

既然切片是引用底層數(shù)組的,需要注意的就是小切片引用大數(shù)組的問題,如果底層的大數(shù)組一直有切片進(jìn)行引用,那么垃圾回收機(jī)制就不會(huì)將其收回,造成內(nèi)存的浪費(fèi),最有效的做法是copy需要的數(shù)據(jù)后再進(jìn)行操作

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 數(shù)組 如[4]int 類型表示一個(gè)四個(gè)整數(shù)的序列。數(shù)組的長(zhǎng)度是固定的,長(zhǎng)度是數(shù)組類型的一部分。數(shù)組可以以常規(guī)的索引...
    sirius_ztz閱讀 533評(píng)論 0 0
  • 數(shù)組數(shù)組是內(nèi)置(build-in)類型,是一組同類型數(shù)據(jù)的集合。 它是值類型,通過從0開始的下標(biāo)索引訪問元素值。 ...
    咕咕鷄閱讀 18,349評(píng)論 0 0
  • 數(shù)組 定義數(shù)組類型 數(shù)組操作 只支持"=="和"!="比較 同類型數(shù)組可以賦值 定義好的長(zhǎng)度是無法修改的 切片 切...
    洛奇lodge閱讀 791評(píng)論 0 0
  • 數(shù)組 數(shù)組是具有相同唯一類型的一組長(zhǎng)度固定的數(shù)據(jù)項(xiàng)序列,這種類型可以是任意的原始類型,例如整形,字符型或者自定義類...
    imuzi閱讀 594評(píng)論 0 0
  • go數(shù)組切片 主要知識(shí)點(diǎn) 數(shù)組(array)類型和切片(slice)都屬于集合類的類型;他們最重要的不同是:數(shù)組類...
    theo_NI閱讀 525評(píng)論 0 0

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