詳解go語言的array和slice 【一】

本篇會詳細講解go語言中的array和slice,和平時開發中使用時須要注意的地方,以避免入坑。數組

  Go語言中array是一組定長的同類型數據集合,而且是連續分配內存空間的。bash

  聲明一個數組

var arr [3]int複製代碼

  數組聲明後,他包含的類型和長度都是不可變的.若是你須要更多的元素,你只能從新建立一個足夠長的數組,並把原來數組的值copy過來。app

  在Go語言中,初始化一個變量後,默認把變量賦值爲指定類型的zero值,如string 的zero值爲"" number類型的zero值爲0.數組也是同樣的,聲明完一個數組後,數組中的每個元素都被初始化爲相應的zero值。如上面的聲明是一個長度爲5的int類型數組。數組中的每個元素都初始化爲int類型的zero值 0函數

  可使用array的字面量來快速建立和初始化一個數組,array的字面量容許你設置array的長度和array中元素的值性能

arr := [3]{1, 2, 3}複製代碼

  若是[]中用...來代替具體的長度值,go會根據後面初始化的元素長度來計算array的長度ui

arr := [...]{1, 2, 3, 4}複製代碼

  若是你想只給某些元素賦值,能夠這樣寫spa

arr := [5]int {1: 5, 3: 200}複製代碼

  上面的語法是建立了一個長度爲5的array,並把index爲1的元素賦值爲0,index爲3的元素賦值爲200,其餘沒有初始化的元素設置爲他們的zero值3d

指針數組

  聲明一個包含有5個整數指針類型的數組,咱們能夠在初始化時給相應位置的元素默認值。下面是給索引爲0的元素一個新建的的int類型指針(默認爲0),給索引爲1的元素指向值v的地址,剩下的沒有指定默認值的元素爲指針的zero值也就是nil指針


var v int = 6
    array := [5]*int{0: new(int), 1: &v}
    fmt.Println(len(array))
    fmt.Println(*array[0])
    fmt.Println(*array[1])
    v2 := 7
    array[2] = &v2
    fmt.Println("------------------")
    for i, v := range array {
        fmt.Printf("index %d, address %v value is ", i, v)
        if v != nil {
            fmt.Print(*v)
        } else {
            fmt.Print("nil")
        }
        fmt.Println(" ")
    }複製代碼

數組作爲函數參數

  好比我個建立一個100萬長度的int類型數組,在64位機上須要在內存上佔用8M的空間,把他作爲一個參數傳遞到一個方法內,go會複製這個數組,這將致使性能的降低。  code

package main

import (
    "fmt"
)

const size int = 1000*1000

func sum(array [size]int) float64 {
    total := 0.0
    for _, v := range array {
        total += float64(v)
    }
    return total
}

func main() {    
    var arr [size]int            
    fmt.Println(sum(arr))    
}複製代碼

  固然go也提供了其餘的方式,能夠用指向數組的指針作爲方法的參數,這樣在傳參的時候會傳遞array的地址,只須要複製8個字節,


package main

import (
    "fmt"
)

const size int = 1000*1000

func sum(array *[size]int) float64 {
    total := 0.0
    for _, v := range array {
        total += float64(v)
    }
    return total
}

func main() {    
    var arr [size]int            
    fmt.Println(sum(&arr))    
}複製代碼

slice

  slice能夠被認爲動態數組,在內存中也是連續分配的。他能夠動態的調整長度,能夠經過內置的方法append來自動的增加slice長度;也能夠經過再次切片來減小slice的長度。

  slice的內部結構有3個字段,分別是維護的底層數組的指針,長度(元素個數)和容量(元素可增加個數,不足時會增加),下面咱們定義一個有2個長度,容量爲5的slice


func main() {    
    s := make([]int, 2, 5)
    fmt.Println("len: ", len(s))
    fmt.Println("cap: ", cap(s))
    s = append(s, 2)
    fmt.Println("--------")
    
    fmt.Println("len: ", len(s))
    fmt.Println("cap: ", cap(s))    
    fmt.Println("--------")
    s[0] = 12
    for _, v := range s {
        fmt.Println(v)
    }
}複製代碼

  初始化slice後,他的長度爲2,也就是元素個數爲2,由於咱們沒有給任何一個元素賦值,因此爲int的zero值,也就是0.能夠用len和cap看一下這個slice的長度和容量。

  用append給這個slice添加新值,返回一個新的slice,若是容量不夠時,go會自動增長容易量,小於一1000個長度時成倍的增加,大於1000個長度時會以1.25或者25%的位數增加。

 上面的代碼執行完後,slice的結構以下

Slice 的聲明和初始化

  建立和初始化一個slice有幾種不一樣的方式,下面我會一一介紹

使用make聲明一個slice  

slice1 := make([]int, 3)
    fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)
    slice1 = append(slice1, 1)
    fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)複製代碼

  make([]int, 3) 聲明瞭個長度爲3的slice,容量也是3。下面的append方法會添加一個新元素到slice裏,長度和容量都會發生變化。

  輸出結果:

len:  3 cap:  3 array : [0 0 0]
len:  4 cap:  6 array : [0 0 0 1]複製代碼

  也能夠經過重載方法指定slice的容量,下面:

slice2 := make([]int, 3, 7)
    fmt.Println("len: ", len(slice2), "cap: ", cap(slice2), "array :", slice2)複製代碼

輸出長度爲3,容量爲7

使用slice字變量

  使用字變量來建立Slice,有點像建立一個Array,可是不須要在[]指定長度,這也是Slice和Array的區別。Slice根據初始化的數據來計算度和容量

  建立一個長度和容量爲5的Slice

slice3 := []int{1, 2, 3, 4, 5}
    fmt.Println("len: ", len(slice3), "cap: ", cap(slice3), "array :", slice3)複製代碼

  也能夠經過索引來指定Slice的長度和容量,

  下面建立了一個長度和容量爲6的slice

slice4 := []int{5: 0}
    fmt.Println("len: ", len(slice4), "cap: ", cap(slice4), "array :", slice4)複製代碼

  聲明nil Slice,內部結構的指針爲nil。能夠用append給slice填加新的元素,內部的指針指向一個新的數組

var slice5 []int
    fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)
    slice5 = append(slice5, 4)
    fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)複製代碼

  

  建立空Slice有兩種方式


slice6 := []int{}
    fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
    slice6 = append(slice6, 2)
    fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)

    slice7 := make([]int, 0)
    fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)
    slice7 = append(slice7, 7)
    fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)複製代碼

slice的切片

  

// 建立一個容量和長度均爲6的slice
    slice1 := []int{5, 23, 10, 2, 61, 33}
    // 對slices1進行切片,長度爲2容量爲4
    slice2 := slice1[1:3]
    fmt.Println("cap", cap(slice2))
    fmt.Println("slice2", slice2)複製代碼

  slice1的底層是一個容量爲6的數組,slice2指底層指向slice1的底層數組,但起始位置爲array的第一個元素也就是23.由於slices2從索引1開始的,因此沒法訪問底層數組索引1以前的元素,也沒法訪問容量以後的元素。能夠看下圖理解一下。

  新建立的切片長度和容量的計算

  對於一個新slice[x:y] 底層數組容量爲z,

  x: 新切片開始的元素的索引位置,上面的slice1[1:3]中的1就是起始索引

  y:新切片但願包含的元素個數,上面的slice1[1:3],但願包含2個底層數組的元素 1+2=3

  容量: z-x 上面的slice1[1:3] 底層數組的容量爲6, 6-1=5因此新切片的容量爲5

修改切片致使的後果

  因爲新建立的slice2和slice1底層是同一個數組,因此修改任何一個,兩個slice共同的指向元素,會致使同時修改的問題

  


// 建立一個容量和長度均爲6的slice
    slice1 := []int{5, 23, 10, 2, 61, 33}
    // 對slices1進行切片,長度爲2容量爲4
    slice2 := slice1[1:3]
    fmt.Println("cap", cap(slice2))
    fmt.Println("slice2", slice2)

    //修改一個共同指向的元素
    //兩個slice的值都會修改
    slice2[0] = 11111
    fmt.Println("slice1", slice1)
    fmt.Println("slice2", slice2)複製代碼

  如圖所示

  需注意的是,slice只能訪問其長度範圍內的元素,若是超出長度會報錯。

  除了修改共同指向元素外,若是新建立的切片長度小於容量,新增元素也會致使原來元素的變更。slice增長新元素使用內置的方法append。append方法會建立一個新切片。


// 建立一個容量和長度均爲6的slice
    slice1 := []int{5, 23, 10, 2, 61, 33}
    // 對slices1進行切片,長度爲2容量爲4
    slice2 := slice1[1:3]
    fmt.Println("cap", cap(slice2))
    fmt.Println("slice2", slice2)

    //修改一個共同指向的元素
    //兩個slice的值都會修改
    slice2[0] = 11111
    fmt.Println("slice1", slice1)
    fmt.Println("slice2", slice2)

    // 增長一個元素
    slice2 = append(slice2, 55555)

    fmt.Println(slice1)
    fmt.Println(slice2)複製代碼

若是切片的容量足夠就把新元素合添加到切片的長度。若是底層的的數組容量不夠時,會從新建立一個新的數組並把現有元素複製過去。

slice3 := []int{1, 2, 3}
    fmt.Println("slice2 cap", cap(slice3))

    slice3 = append(slice3, 5)
    fmt.Println("slice2 cap", cap(slice3))複製代碼

輸出的結果爲:

slice2 cap 3
slice2 cap 6複製代碼

容量增加了一倍。

控制新建立slice的容量

  建立一個新的slice的時候能夠限制他的容量

// 建立一個容量和長度均爲6的slice
    slice1 := []int{5, 23, 10, 2, 61, 33}
    // 對slices1進行切片,長度爲2容量爲3
    slice2 := slice1[1:3:4]
    fmt.Println("cap", cap(slice2))
    fmt.Println("slice2", slice2)複製代碼

  slice2長度爲2容量爲3,這也是經過上面的公式算出來的

  長度:y-x 3-1=2

  容量:z-x 4-1= 3

須要注意的是容量的長度不能大於底層數組的容量

綠顏色表示slice2中的元素,黃顏色表示容量中示使用的元素。可是須要注意的是,咱們修改或者增長slice2容量範圍內的元素個數依然會修改slice1。


// 建立一個容量和長度均爲6的slice
    slice1 := []int{5, 23, 10, 2, 61, 33}
    // 對slices1進行切片,長度爲2容量爲3
    slice2 := slice1[1:3:4]
    fmt.Println("cap", cap(slice2))
    fmt.Println("slice2", slice2)

    //修改一個共同指向的元素
    //兩個slice的值都會修改
    slice2[0] = 11111
    fmt.Println("slice1", slice1)
    fmt.Println("slice2", slice2)

    // 增長一個元素
    slice2 = append(slice2, 55555)

    fmt.Println(slice1)
    fmt.Println(slice2)複製代碼

相關文章
相關標籤/搜索