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開始計算。

聲明、初始化和訪問數組

因為Go中的數組要求數據類型固定、長度固定,所以在聲明的時候需要給定長度和數據類型。

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

var arr_name [5]int

必須注意,雖然我們稱呼數組為int類型的數組,但數組的數據類型是兩部分組成的[n]TYPE,這個整體才是數組的數據類型。所以,[5]int[6]int是兩種不同的數組類型。不同數據類型,意味着如果數組賦值給另一數組時需要數據類型轉換操作,而Go默認是不會進行數據類型轉換的。

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

數組也是一種變量類型,也會被初始化。初始化的方式是數組中的所有元素都根據數據類型賦值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,其它的元素(第1、4、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}}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM