標籤:附加 運行 表示 color 資料結構 效能 常用 位置 學習
7、數組:
幾乎是最常用的資料類型了。。。
數組就是指一系列同一類型資料 的集合。數組中包含的每個資料被稱為數組元素(element),一個數組包含的元素個數被稱為數 組的長度。
常規的數組聲明方法:
[32]byte // 長度為32的數組,每個元素為一個位元組[2*N] struct {x , y int32} //複雜類型數組[1000]*float32 //指標數組[3][5]int //二維數組[2][2][2]float64 // 等同於[2]([2]([2]float64))
在Go語言中,數組長度在定義後就不可更改,在聲明時間長度度可以為一個常量或者一個常量 運算式(常量運算式是指在編譯期即可計算結果的運算式)。數組的長度是該數群組類型的一個內 置常量,可以用Go語言的內建函數len()來擷取。下面是一個擷取數組arr元素個數的寫法:
arrLength := len(arr)
a、元素訪問
可以使用數組下標來訪問數組中的元素。數組下標從0開始,len(array)-1 則表示最後一個元素的下標。下面的樣本遍曆整型數組並逐個列印元素內容:
for i := 0; i < len(array); i++ { fmt.Println("Element", i, "of array is", array[i])}
還可以用range遍曆
for i, v := range array { fmt.Println("Array element[", i, "]=", v)}
range具有兩個傳回值,第一個傳回值是元素的數組下標,第二個傳回值是元素的值。
b、實值型別
需要特別注意的是,在Go語言中數組是一個實值型別(value type)。所有的實值型別變數在賦值和作為參數傳遞時都將產生一次複製動作。
如果將數組作為函數的參數類型,則在函數調用時該 參數將發生資料複製。因此,在函數體中無法修改傳入的數組的內容,因為函數內操作的只是所 傳入數組的一個副本。
舉例:
package mainimport "fmt"func modify(array [10]int) { array[0] = 10 // 試圖修改數組的第一個元素
fmt.Println("In modify(), array values:", array)}func main() { array := [5]int{1,2,3,4,5} // 定義並初始化一個數組 modify(array) // 傳遞給一個函數,並試圖在函數體內修改這個數組內容 fmt.Println("In main(), array values:", array)}
該程式的執行結果為:
In modify(), array values: [10 2 3 4 5]
In main(), array values: [1 2 3 4 5]
從執行結果可以看出,函數modify()內操作的那個數組跟main()中傳入的數組是兩個不同的實 例。那麼,如何才能在函數內操作外部的資料結構呢?可以用數組切片功能來達成這個目標。
8、數組切片:
在前一節裡我們已經提過數組的特點:數組的長度在定義之後無法再次修改;數組是實值型別, 每次傳遞都將產生一份副本。顯然這種資料結構無法完全滿足開發人員的真實需求。
不用失望,Go語言提供了數組切片(slice)這個非常酷的功能來彌補數組的不足。
初看起來,數組切片就像一個指向數組的指標,實際上它擁有自己的資料結構,而不僅僅是 個指標。數組切片的資料結構可以抽象為以下3個變數:
? 一個指向原生數組的指標;
? 數組切片中的元素個數;
? 數組切片已指派的儲存空間。
從底層實現的角度來看,數組切片實際上仍然使用數組來管理元素,因此它們之間的關係讓 C++程式員們很容易聯想起STL中std::vector和數組的關係。基於數組,數組切片添加了一系 列管理功能,可以隨時動態擴充存放空間,並且可以被隨意傳遞而不會導致所管理的元素被重複 複製。
a、建立數組切片
建立數組切片的方法主要有兩種——基於數組和直接建立,下面我們來簡要介紹一下這兩種方法。
? 基於數組
數組切片可以基於一個已存在的數組建立。數組切片可以只使用數組的一部分元素或者整個數組來建立,甚至可以建立一個比所基於的數組還要大的數組切片。
代碼清單2-1(slice.go)示範了如何基於一個數組的前5個元素建立一個數組切片。
package mainimport "fmt"func main() { // 先定義一個數組 var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 基於數組建立一個數組切片 var mySlice []int = myArray[:5] fmt.Println("Elements of myArray: ") for _, v := range myArray { fmt.Print(v, " ") } fmt.Println("\nElements of mySlice: ") for _, v := range mySlice { fmt.Print(v, " ") } fmt.Println()}
運行結果為:
Elements of myArray:
1 2 3 4 5 6 7 8 9 10
Elements of mySlice:
1 2 3 4 5
Go語言支援用myArray[first:last]這樣的方式來基於數組產生一 個數組切片,而且這個用法還很靈活,比如下面幾種都是合法的。
基於myArray的所有元素建立數組切片:
mySlice = myArray[:]
基於myArray的前5個元素建立數組切片:
mySlice = myArray[:5]
基於從第5個元素開始的所有元素建立數組切片:
mySlice = myArray[5:]
? 直接建立( make() )
並非一定要事先準備一個數組才能建立數組切片。Go語言提供的內建函數make()可以用於
靈活地建立數組切片。下面的例子示範了直接建立數組切片的各種方法。
建立一個初始元素個數為5的數組切片,元素初始值為0:
mySlice1 := make([]int, 5)
建立一個初始元素個數為5的數組切片,元素初始值為0,並預留10個元素的儲存空間:
mySlice2 := make([]int, 5, 10)
直接建立並初始化包含5個元素的數組切片:
mySlice3 := []int{1, 2, 3, 4, 5}
事實上還會有一個匿名數組被建立出來,只是不需要我們來操心而已。
b、元素遍曆
運算元組元素的所有方法都適用於數組切片,比如數組切片也可以按下標讀寫元素,用len() 函數擷取元素個數,並支援使用range關鍵字來快速遍曆所有元素。
傳統的元素遍曆方法如下:
for i := 0; i <len(mySlice); i++ { fmt.Println("mySlice[", i, "] =", mySlice[i])}
使用range關鍵字可以讓遍曆代碼顯得更整潔。range運算式有兩個傳回值,第一個是索引,
第二個是元素的值:
for i, v := range mySlice { fmt.Println("mySlice[", i, "] =", v)}
對比上面的兩個方法,我們可以很容易地看出使用range的代碼更簡單易懂。
c、動態增減元素
可動態增減元素是數組切片比數組更為強大的功能。與數組相比,數組切片多了一個儲存能 力(capacity)的概念,即元素個數和分配的空間可以是兩個不同的值。合理地設定儲存能力的 值,可以大幅降低數組切片內部重新分配記憶體和搬送記憶體塊的頻率,從而大大提高程式效能。
假如你明確知道當前建立的數組切片最多可能需要儲存的元素個數為50,那麼如果你設定的 儲存能力小於50,比如20,那麼在元素超過20時,底層將會發生至少一次這樣的動作——重新分 配一塊“夠大”的記憶體,並且需要把內容從原來的記憶體塊複製到新分配的記憶體塊,這會產生比較 明顯的開銷。給“夠大”這兩個字加上引號的原因是系統並不知道多大才是夠大,所以只是一個 簡單的猜測。比如,將原有的記憶體空間擴大兩倍,但兩倍並不一定夠,所以之前提到的記憶體重新 分配和內容複寫的過程很有可能發生多次,從而明顯降低系統的整體效能。但如果你知道最大是 50並且一開始就設定儲存能力為50,那麼之後就不會發生這樣非常耗費CPU的動作,從而達到空間換時間的效果。
數組切片支援Go語言內建的cap()函數和len()函數,代碼清單2-2( slice2.go )簡單示範了這兩個內建函數的用法。可以看出,cap()函數返回的是數組切片分配的空間大小,而len()函數返回的是 數組切片中當前所儲存的元素個數。
package mainimport "fmt" func main() { mySlice := make([]int, 5, 10) fmt.Println("len(mySlice):", len(mySlice)) fmt.Println("cap(mySlice):", cap(mySlice)) }
該程式的輸出結果為:
len(mySlice): 5
cap(mySlice): 10
如果需要往上例中mySlice已包含的5個元素後面繼續新增元素,可以使用append()函數。 下面的代碼可以從尾端給mySlice加上3個元素,從而產生一個新的數組切片:
mySlice = append(mySlice, 1, 2, 3)
函數append()的第二個參數其實是一個不定參數,我們可以按自己需求添加若干個元素,
甚至直接將一個數組切片追加到另一個數組切片的末尾:
mySlice2 := []int{8, 9, 10}// 給mySlice後面添加另一個數組切片mySlice = append(mySlice, mySlice2...)
需要注意的是,我們在第二個參數mySlice2後面加了三個點,即一個省略符號,如果沒有這個省 略號的話,會有編譯錯誤,因為按append()的語義,從第二個參數起的所有參數都是待附加的元素。因為mySlice中的元素類型為int,所以直接傳遞mySlice2是行不通的。加上省略符號相當於把mySlice2包含的所有元素打散後傳入。
數組切片會自動處理儲存空間不足的問題。如果追加的內容長度超過當前已指派的儲存空間(即cap()調用返回的資訊),數組切片會自動分配一塊足夠大的記憶體。
d、基於數組切片建立數組切片
類似於數組切片可以基於一個數組建立,數組切片也可以基於另一個數組切片建立。下面的 例子基於一個已有數組切片建立新數組切片:
oldSlice := []int{1, 2, 3, 4, 5}newSlice := oldSlice[:3] // 基於oldSlice的前3個元素構建新數組切片
e、內容複寫
數組切片支援Go語言的另一個內建函數copy(),用於將內容從一個數組切片複製到另一個 數組切片。如果加入的兩個數組切片不一樣大,就會按其中較小的那個數組切片的元素個數進行 複製。下面的樣本展示了copy()函數的行為:
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3}copy(slice2, slice1) // 只會複製slice1的前3個元素到slice2中 copy(slice1, slice2) // 只會複製slice2的3個元素到slice1的前3個位置
未完待續。。。
【GO】GO語言學習筆記三