go基礎系列:數組

瞭解Python、Perl、JavaScript的人想必都知道它們的數組是動態的,能夠隨需求自動增大數組長度。但Go中的數組是固定長度的,數組一經聲明,就沒法擴大、縮減數組的長度。但Go中也有相似的動態"數組",稱爲slice數據結構,在下一篇文章會詳細解釋它。數組

Go中的數組是slice和map兩種數據類型的基礎,這兩種數據類型的底層都是經過數組實現的。緩存

數組的存儲方式

當在Go中聲明一個數組以後,會在內存中開闢一段固定長度的、連續的空間存放數組中的各個元素,這些元素的數據類型徹底相同,能夠是內置的簡單數據類型(int、string等),也能夠是自定義的struct類型。數據結構

  • 固定長度:這意味着數組不可增加、不可縮減。想要擴展數組,只能建立新數組,將原數組的元素複製到新數組
  • 連續空間:這意味能夠在緩存中保留的時間更長,搜索速度更快,是一種很是高效的數據結構,同時還意味着能夠經過數值index的方式訪問數組中的某個元素
  • 數據類型:意味着限制了每一個block中能夠存放什麼樣的數據,以及每一個block能夠存放多少字節的數據

例如,使用下面的語句聲明一個長度爲4的int類型的數組,那麼這個數組最多隻能存放4個元素,且全部元素都只能是int類型。同時,還爲這個數組作了初始化。函數

arr_name := [4]int{3,5,22,12}

這個數組的結構以下圖所示:指針

其中左上角的小格子中的數表示各元素所在數組中的位置,也就是它們對應的index,index從0開始計算。code

聲明、初始化和訪問數組

由於Go中的數組要求數據類型固定、長度固定,因此在聲明的時候須要給定長度和數據類型。對象

例如聲明一個長度爲五、數據類型爲int的數組,名爲arr_name。blog

var arr_name [5]int

必須注意,雖然咱們稱呼數組爲int類型的數組,但數組的數據類型是兩部分組成的[n]TYPE,這個總體纔是數組的數據類型。因此,[5]int[6]int是兩種不一樣的數組類型。不一樣數據類型,意味着若是數組賦值給另外一數組時須要數據類型轉換操做,而Go默認是不會進行數據類型轉換的。索引

在Go中,當一個變量被聲明以後,都會當即對其進行默認的賦0初始化。對int類型的變量會默認初始化爲0,對string類型的變量會初始化爲空"",對布爾類型的變量會初始化爲false,對指針(引用)類型的變量會初始化爲nil。ip

數組也是一種變量類型,也會被初始化。初始化的方式是數組中的全部元素都根據數據類型賦值0。例如int類型的數組,元素所有賦值爲0,string類型的數組,元素所有賦值爲""等。

因此,上面聲明數組arr_name以後,它初始化後的結果以下:

能夠直接輸出數組:

import "fmt"
var new_arr [3]int
fmt.Println(new_arr) // 輸出:[0 0 0]

能夠將數組的聲明和初始化爲給定值的操做合併:

arr_name := [5]int{3,5,22,12,23}

若是將元素個數指定爲特殊符號...,則表示經過初始化時的給定的值個數來推斷數組長度:

// 聲明長度爲3的數組
arr_name1 := [...]int{2,3,4}

// 聲明長度爲4的數組
arr_name2 := [...]int{2,3,4,5}

若是聲明數組時,只想給其中某幾個元素初始化賦值,則使用索引號:

arr_name := [5]int{1:10, 2:20}

這表示聲明長度爲5的數組,但第2個元素的值爲10,第3個元素的值爲20,其它的元素(第一、四、5個元素)都默認初始化爲0。

這個數組聲明後的結果以下:

要訪問數組中的某個元素,可使用索引:

arr_name := [5]int{2,3,4,5,6}

// 訪問數組的第4個元素,將輸出:5
print(arr_name[3])

// 修改數組第3個元素的值
arr_name[2] = 22

指針數組(引用)

能夠聲明一個指針類型的數組,這樣數組中就能夠存放指針。注意,指針的默認初始化值爲nil。

例如,建立一個指向int類型的數組:

arr_name := [5]*int{1:new(int), 3:new(int)}

上面的*int表示數組只能存儲*int類型的數據,也就是指向int的指針類型。new(TYPE)函數會爲一個TYPE類型的數據結構劃份內存並作默認初始化操做,並返回這個數據對象的指針,因此new(int)表示建立一個int類型的數據對象,同時返回指向這個對象的指針。

初始化後,它的結構以下:注意int指針指向的數據對象會被初始化爲0。

對數組中的指針元素進行賦值:

package main

import "fmt"

func main() {
    arr_name := [5]*int{1:new(int), 3:new(int)}
    *arr_name[1]=10
    *arr_name[3]=30
    
    // 賦值一個新元素
    arr_name[4]=new(int)
    
    fmt.Println(*arr_name[1])
    fmt.Println(*arr_name[3])
    fmt.Println(*arr_name[4])
}

數組拷貝

在Go中,因爲數組算是一個值類型,因此能夠將它賦值給其它數組。

由於數組類型的完整定義爲[n]TYPE,因此數組賦值給其它數組的時候,n和TYPE必須相同。

例如:

// 聲明一個長度爲5的string數組
var str_arr1 [5]string

// 聲明並初始化另外一個string數組
str_arr2 := [5]string{"Perl","Shell","Python","Go","Java"}

// 將str_arr2拷貝給str_arr1
str_arr1 = str_arr2

數組賦值給其它數組時,其實是完整地拷貝一個數組。因此,若是數組是一個指針型的數組,那麼拷貝的將是指針數組,而不會拷貝指針所指向的對象。

package main

import "fmt"

func main() {
    var str_arr1 [3]*string
    str_arr2 := [3]*string{
        new(string),
        new(string),
        new(string),
    }
    *str_arr2[0] = "Perl"
    *str_arr2[1] = "Python"
    *str_arr2[2] = "Shell"
    
    // 數組賦值,拷貝指針自己,而不拷貝指向的值
    str_arr1 = str_arr2
    
    // 將輸出Python
    fmt.Println(*str_arr1[1])
}

拷貝後,它的結構以下:

array遍歷迭代

range關鍵字能夠對array進行迭代,每次返回一個index和對應的元素值。能夠將range的迭代結合for循環對array進行遍歷。

package main

func main() {
    my_arr := [4]int{11,22,33,44}
    for index,value := range my_arr {
        println("index:",index," , ","value",value)
    }
}

輸出結果:

index: 0  ,  value 11
index: 1  ,  value 22
index: 2  ,  value 33
index: 3  ,  value 44

傳遞數組參數給函數

Go中的傳值方式是按值傳遞,這意味着給變量賦值、給函數傳參時,都是直接拷貝一個副本而後將副本賦值給對方的。這樣的拷貝方式意味着:

  • 若是數據結構體積龐大,則要完整拷貝一個數據結構副本時效率會很低
  • 函數內部修改數據結構時,只能在函數內部生效,函數一退出就失效了,由於它修改的是副本對象

數組一樣也遵循此規則。對於數組的賦值,上面數組拷貝中已經解釋過了。若是函數的參數是數組類型,那麼調用函數時傳遞給函數的數組也同樣是這個數組拷貝後的一個副本。

例如,建立一個100W個元素的數組,將其傳遞給函數foo():

var big_arr [1e6]int

func foo(a [1e6]int) {
    ...
}

// 調用foo
foo(bigarr)

當上面聲明big_arr後,就有100W個元素,假設這個int佔用8字節,整個數組就佔用800W字節,大約有8M數據。當調用foo的時候,Go會直接複製這8M數據造成另外一個數組副本,並將這個副本交給foo進行處理。在foo中處理的數組,其實是這個副本,foo()不會對原始的big_arr產生任何影響。

能夠將數組的指針傳遞給函數,這樣指針傳遞給函數時,複製給函數參數的是這個指針,總共才8個字節(每一個指針佔用1個機器字長,64位機器上是64bit共佔用8字節),複製的數據量很是少。並且,由於複製的是指針,foo()修改這個數組時,會直接影響原始數組。

var big_arr [1e6]int

// 生成數組的指針
ref_big_arr := &big_arr

func foo(ra *[1e6]int) {
    ...
}

// 調用foo,傳遞指針
foo(ref_big_arr)

多維數組

能夠經過組合兩個一維數組的方式構成二維數組。通常在處理具備父、子關係或者有座標關係的數據時,二維數組比較有用。

例如,聲明二維數組:

var t_arr [4][2]int

這表示數組有4個元素,每一個元素都是一個包含2元素的小數組。換一種方式,例如:

t_arr := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}

還能夠指定位置進行初始化:

t_arr := [4][2]int{1: {20, 21}, 3: {40, 41}}
t_arr := [4][2]int{1: {0: 20}, 3: {1: 41}}

相關文章
相關標籤/搜索