Array
1 | // n 陣列元素數量 |
- 長度是固定的, 聲明後無法被改變
- 長度是陣列類型的一部份, 所以兩個長度不同但元素類型相同的陣列, 是不同的類型, ex: [2]int 跟[3]int是不同的類型.
初始化方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14a := [3]int{1,2,3}
b := [...]int{1,2,3,4} //透過初始化給的元素數量來給定長度
c := [3]int{2:100, 1:200} //透過索引初始化元素, 沒被初始化的就是該類型的預設值
d := [...]struct {
name: string
age uint8
} {
{ "user1", 5 },
{ "user2", 18 },
}
// 多維度陣列
aa := [2][3]int{{1,2,3}, {4,5,6}}
bb := [...][3]int{{1,2,3}, {4,5,6}} //只有第一個維度能用...
操作方法
1 | // 取值 |
Array的傳遞
1 | package main |
3個都是[2]int的記憶體位置都不同, 這很明顯Go在Array的賦值和傳參數都是value type,靠複製整個Array的, 因此如果是1億數量的int64陣列, 一個元素佔64bits, 那這陣列就要800MB, 這樣copy 瞬間會需要1.6GB的記憶體空間.
所以也能改成方法參數傳指針, 來避掉這問題.
1 | package main |
會看到都是操作同一個位置的陣列了.
但會引發另一個問題, 原來陣列的指針指向改變了, 函數內的也會更著變動.
這兩個問題, Slice都能有效的處理解決.
Slice
動態分配大小的Array, 可以不必事先指定大小.
雖然是這樣講, 但他其實是一個結構, 透過ptr指向引用的底層Array.
1 | // type 元素類型 |
初始化方式
從現有的array或是slice生出新的slice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56package main
import (
"fmt"
"reflect"
)
func main() {
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// 取開始到中間的所有元素
slice0 := arr[:5]
fmt.Println(slice0)
// 取中間到尾部的所有元素
slice1 := arr[5:]
fmt.Println(slice1)
// 取中間區間的所有元素
slice2 := arr[2:7]
fmt.Println(slice2)
// 取所有元素, 表示原有的slice
slice3 := arr[:]
fmt.Println(slice3)
// 重置slice, 清空擁有的元素
slice4 := arr[0:0]
fmt.Println(slice4)
fmt.Println("-------------------")
slice := arr[1:3]
fmt.Println(reflect.TypeOf(arr))
fmt.Println(reflect.TypeOf(slice))
fmt.Println(len(slice), cap(slice))
fmt.Println(arr)
fmt.Println(slice)
fmt.Println("-------------------")
slice[0] = 0
fmt.Println(arr)
fmt.Println(slice)
}
/*
[0 1 2 3 4]
[5 6 7 8 9]
[2 3 4 5 6]
[0 1 2 3 4 5 6 7 8 9]
[]
-------------------
[10]int
[]int
2 9
[0 1 2 3 4 5 6 7 8 9]
[1 2]
-------------------
[0 0 2 3 4 5 6 7 8 9]
[0 2]
*/宣告slice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package main
import (
"fmt"
)
func main() {
// 宣告字串slice
var numList []int
// 宣告一個空slice
var numEmptyList = []int{}
fmt.Println(numList, numEmptyList)
fmt.Println(len(numList), len(numEmptyList))
fmt.Println(numList == nil)
fmt.Println(numEmptyList == nil)
}
/*
[] []
0 0
true
false
*/
這裡第18行是true, 是因為numList只是宣告, 還沒真正實例化
第19行則是有被實例化被分配到記憶體內了.
因為slice還是個struct動態結構, 所以只能和nil作比較.
- 使用make()
1
2
3
4// type 元素類型
// size 為slice先分配多少個元素的預設值進去
// cap 預分配的數量, 只是能提前分配空間, 降低之後多次分配空間的效能問題.
make([]type, size, cap)
透過make()生成的slice, 一定會實例化配置記憶體,
但透過從其他slice指定開始和結束位置的slice, 只是把新的slice指向舊的slice已經分配好的空間, 只是新的slice註明開始跟結束位子而已, 此時新的slice並不會真的去跟記憶體要一個新的連續空間來宣告新array.
1 | package main |
透過append()添加元素
append()能為slice動態添加數個元素.
當slice不能容納足夠多的元素時, slice就會進行擴容.
“擴容”往往發生在append()被調用時.
擴容時,容量的擴展規律按照容量的2倍在擴容, 例如1、2、4、8.
1 | package main |
可以很明顯看到, 當原來的cap滿的時候, 會產生擴容現象.
舉個生活例子來說明這len和cap以及擴容.
公司發展初期, 資金少, 人員配置也少, 只需要小小的辦公室就能容納所有員工.
隨著業務的擴展和收入的增加, 就需要擴編, 但現有辦公室大小是固定的, 無法改變它.
所以公司決定! 換個更大的辦公室, 每次搬家就要把所有人搬遷到新的辦公處.
員工就是slice中的元素
辦公室就是配置好的記憶體空間, 大小是固定的
搬家就是重新配置
不論搬家多少次, 公司名稱都是固定的, 表示外部使用這slice的變數名稱是不會修改的,
但因為搬家後地址發生變化, 所以slice內部array指向的地址會有所修改.
1 | // 添加多個元素 |
More example
1 | package main |
1 | package main |
1 | package main |
透過copy()複製slice到令一個slice
Go內建copy()方法, 可以快速的把slice 作copy
1 | // 回傳有多少個元素被複製過去 |
1 | package main |
- copyA宣告的容量是來源的既有元素數量, 所以能完整copy來源所有元素.
- copyB只宣告了3個容量的slice, 之前提過slice可以取開始和結束區間, 這裡用這方式來取值作copy
- copyC一樣容量只有3, 但要複製來源所有元素時, 卻因為容量不夠, 所以沒法複製全部. 又因為擴容只會發生在append, 因此這例子不會自動擴容, 導致後半段資料全被切掉.
刪除slice中的元素
因為slice並沒有提供刪除專用的api.
所以只能用本身特性來刪除元素.
本質操作上就是, 以被刪除的元素位置為分界點, 將該元素的前後兩個部份作拼接.
1 | package main |
因為slice如果頻繁刪除新增裡面的元素的話,
是會頻繁的搬動位置, 這點對效能損耗較高.
可能就要考慮其他資料結構來實做.