go——陣列(二)
1.內部實現
在Go語言裡,陣列是一個長度固定的資料型別,用於儲存一段具有相同的型別的元素的連續塊。
陣列儲存的型別可以是內建型別,如整型或字串,也可以是某種結構型別。
灰格子代表數組裡面的元素,每個元素都緊鄰另一個元素。
每個元素都屬於相同的型別,並且每個元素可以用一個唯一的索引(也稱下標或標號)來訪問。
陣列是一種非常有用的資料結構,因為其佔用的記憶體是連續分配的。
由於記憶體連續,CPU能把正在使用的資料快取更久的時間。
而且記憶體連續很容易計算索引,可以快速迭代數組裡的所有元素。
陣列的型別資訊可以提供每次訪問一個元素時需要在記憶體中移動的距離。
既然陣列的每個元素型別相同,又是連續分配,就可以以固定速度索引陣列中的任意資料,速度非常快。
2.宣告和初始化
宣告陣列時需要指定內部儲存的資料型別,以及需要儲存的元素的數量,這個數量也稱為陣列的長度。
(1)宣告一個數組,並設定為零值
//宣告一個包含5個元素的整型陣列 var array [5]int
一旦宣告,數組裡儲存的資料型別和長度就都不能改變了。
如果需要儲存更多的元素,就需要先建立一個更長的資料,再把原來數組裡的值複製到新數組裡。
在Go語言中宣告變數時,總會使用對應型別的零值來對變數進行初始化。陣列也不例外。
當陣列初始化時,陣列內每個元素都初始化為對應型別的零值。
整型數組裡的每個元素都初始化為0,也就是整數的零值。
(2)使用陣列字面量宣告陣列
一種快速建立陣列並初始化的方式是使用陣列字面量。
陣列字面量允許宣告數組裡元素的數量同時指定為每個元素的值。
//宣告一個包含5個元素的整型陣列 //用具體值初始化每個元素 array := [5]int{10,20,30,40,50}
(3)讓Go自動計算宣告陣列的長度
如果使用...代替陣列的長度,Go語言會根據初始化時陣列元素的數量來確定該陣列的長度。
//宣告一個整型陣列 //用具體值初始化每個元素 //容量由初始化值的數量決定 array := [...]int{10,20,30,40,50}
(4)宣告陣列並指定特定元素的值
如果知道陣列的長度,而且準備給每個值都指定具體值,就可以通過索引來進行指定。
//宣告一個有5個元素的陣列 //用具體值初始化索引為1和2的元素 //其餘元素保持零值 array := [5]int{1:20, 2: 30}
3.使用陣列
(1)訪問陣列元素
因為記憶體佈局是連續的,所以陣列是效率很高的資料結構。
在訪問數組裡任意元素的時候,這種高效都是陣列的優勢。
要訪問數組裡某個單獨元素,使用[]運算子。
//宣告一個包含5個元素的整型陣列 //用具體值初始為每個元素 array := [5]int{10,20,30,40,50} //修改索引為2的元素的值 array[2] = 35
(2)訪問指標陣列的元素
宣告一個所有元素都是指標的陣列。使用*運算子就可以訪問元素指標所指向的值
//宣告包含5個元素的指向整數的陣列 //用整型指標初始化索引為0和1的陣列元素 array := [5]*int(0: new(int), 1: new(int)) //為索引為0和1的元素賦值 *array[0] = 10 *array[1] = 20
(3)把同樣型別的一個數組賦值給另外一個數組
在Go語言裡,陣列是一個值。這意味著陣列可以用在賦值操作中。
變數名代表整個陣列,因此,同樣型別的陣列可以賦值給另一個數組。
//宣告第一個包含5個元素的字串陣列 var array1 [5]string //宣告第二個包含5個元素的字串陣列 //用顏色初始化陣列 array2 := [5]string{"red", "yellow", "blue", "green", "pink"} //把array2的值賦值給array1 array1 = array2
(4)編譯器會阻止型別不同的陣列互相賦值
陣列變數的型別包括陣列長度和每個元素的型別。
只有這兩部分都相同的陣列,才是型別相同的陣列,才能相互賦值。
//宣告第一個包含4個元素的字串陣列 var array1 [4]string //宣告第二個包含5個元素的字串陣列 //初始化陣列 array2 := [5]string{"red", "blue", "yellow", "green", "pink"} //複製 array1 = array2 //cannot use array2 (type [5]string) as type [4]string in assignment
(5)把一個指標陣列賦值給另一個
複製指標陣列,只會複製指標的值,而不會複製指標所指向的值。
//宣告第一個包含3個元素的指向字串的指標陣列 var array1 [3]*string //宣告第二個包含3個元素的指向字串的指標陣列 //使用字串指標初始化這個陣列 array2 := [3]*string{new(string), new(string), new(string)} //*array[]表示反取元素的值 //賦值 *array2[0] = "blue" *array2[1] = "pink" *array2[2] = "yellow" //複製 array1 = array2
4.多維陣列
陣列本身只有一個維度,不過可以組合多個數組建立多維陣列。
多維陣列很容器管理具有父子關係的資料或者與座標系相關聯的資料。
(1)宣告二維陣列
//宣告一個二維整型陣列,兩個維度分別儲存4個元素和2個元素 var array [4][2]int //使用陣列字面量來宣告並初始化一個二維整型陣列 array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41} //宣告並初始化外層索引為1和2的元素 array := [4][2]int{1: {20, 21}, 2: {30, 31}} //宣告並初始化外層陣列和內層陣列的單個元素 array := [4][2]int{1: {0: 20}, 2: {1: 31}}
(2)訪問二維陣列的元素
為了訪問單個元素,需要反覆組合使用[]操作符。
//宣告一個2X2的二維整型陣列 var array [2][2]int //設定每個元素的整型值 array[0][0] = 10 array[0][1] = 20 array[1][0] = 30 array[1][1] = 40
(3)同樣型別的多維陣列賦值
只要型別一致就可以將多維陣列相互賦值。
多維陣列的型別包括每一維度的長度以及最終儲存在元素中的資料型別。
//宣告兩個不同的二維整型陣列 var array1 [2][2]int var array2 [2][2]int //設定每個元素的整型值 array2[0][0] = 10 array2[0][1] = 20 array2[1][0] = 30 array2[1][1] = 40 //複製 array1 =array2
(4)使用索引為多維陣列賦值
//將array1的索引為1的維度複製到一個同類型的新數組裡面 var array3 [2]int = array1[1] //將外層陣列的索引為1、內層陣列的索引為0的整型值賦值到新的整型變數裡 var value int = array1[1][0]
5.在函式間傳遞陣列
根據記憶體和效能來看,在函式間傳遞陣列是一個開銷很多的操作。
在函式之間傳遞變數時,總是以值的變數傳遞。
如果這個變數是一個數組,意味著整個陣列不管有多長,都會完整複製,並傳遞給函式。
假設現在我們有一個包含100萬個int型別元素的陣列。
在64位架構上,需要800萬字節,也就是8MB記憶體。
(1)使用值傳遞在函式間傳遞大陣列
//宣告一個需要8MB的陣列 var array [1000000]int //將陣列傳遞給foo函式 foo(array) //函式foo接受一個100萬個整型的陣列 func foo(array [1000000]int) { ... }
每次函式foo被呼叫時,必須在棧上分配8MB的記憶體。
之後整個陣列的值被複制到剛剛分配的記憶體裡。
雖然Go的垃圾回收機制還不錯,但是如果大規模呼叫,勢必會有很多資源消耗。
其實我們只需要傳遞指標,這樣只需要8位元組的記憶體分配給指標就可以了。
(2)使用指標在函式間傳遞大陣列
//宣告一個需要8MB的陣列 var array [1000000]int //將陣列傳遞給foo函式 foo(&array) //函式foo接受一個100萬個整型的陣列 func foo(array *[1000000]int) { ... }