本篇會詳細講解go語言中的array和slice,和平時開發中使用時需要注意的地方,以免入坑。
Go語言中array是一組定長的同型別資料集合,並且是連續分配記憶體空間的。
宣告一個陣列
var arr [3]int複製程式碼
陣列宣告後,他包含的型別和長度都是不可變的.如果你需要更多的元素,你只能重新建立一個足夠長的陣列,並把原來陣列的值copy過來。
在Go語言中,初始化一個變數後,預設把變數賦值為指定型別的zero值,如string 的zero值為"" number型別的zero值為0.陣列也是一樣的,宣告完一個陣列後,陣列中的每一個元素都被初始化為相應的zero值。如上面的宣告是一個長度為5的int型別陣列。陣列中的每一個元素都初始化為int型別的zero值 0
可以使用array的字面量來快速建立和初始化一個陣列,array的字面量允許你設定array的長度和array中元素的值
arr := [3]{1, 2, 3}複製程式碼
如果[]中用...來代替具體的長度值,go會根據後面初始化的元素長度來計算array的長度
arr := [...]{1, 2, 3, 4}複製程式碼
如果你想只給某些元素賦值,可以這樣寫
arr := [5]int {1: 5, 3: 200}複製程式碼
上面的語法是建立了一個長度為5的array,並把index為1的元素賦值為0,index為3的元素賦值為200,其他沒有初始化的元素設定為他們的zero值
指標陣列
宣告一個包含有5個整數指標型別的陣列,我們可以在初始化時給相應位置的元素預設值。下面是給索引為0的元素一個新建的的int型別指標(預設為0),給索引為1的元素指向值v的地址,剩下的沒有指定預設值的元素為指標的zero值也就是nil
var v int = 6
array := [5]*int{0: new(int), 1: &v}
fmt.Println(len(array))
fmt.Println(*array[0])
fmt.Println(*array[1])
v2 := 7
array[2] = &v2
fmt.Println("------------------")
for i, v := range array {
fmt.Printf("index %d, address %v value is ", i, v)
if v != nil {
fmt.Print(*v)
} else {
fmt.Print("nil")
}
fmt.Println(" ")
}複製程式碼
陣列做為函式引數
比如我個建立一個100萬長度的int型別陣列,在64位機上需要在記憶體上佔用8M的空間,把他做為一個引數傳遞到一個方法內,go會複製這個陣列,這將導致效能的下降。
package main
import (
"fmt"
)
const size int = 1000*1000
func sum(array [size]int) float64 {
total := 0.0
for _, v := range array {
total += float64(v)
}
return total
}
func main() {
var arr [size]int
fmt.Println(sum(arr))
}複製程式碼
當然go也提供了其他的方式,可以用指向陣列的指標做為方法的引數,這樣在傳參的時候會傳遞array的地址,只需要複製8個位元組,
package main
import (
"fmt"
)
const size int = 1000*1000
func sum(array *[size]int) float64 {
total := 0.0
for _, v := range array {
total += float64(v)
}
return total
}
func main() {
var arr [size]int
fmt.Println(sum(&arr))
}複製程式碼
slice
slice可以被認為動態陣列,在記憶體中也是連續分配的。他可以動態的調整長度,可以通過內建的方法append來自動的增長slice長度;也可以通過再次切片來減少slice的長度。
slice的內部結構有3個欄位,分別是維護的底層陣列的指標,長度(元素個數)和容量(元素可增長個數,不足時會增長),下面我們定義一個有2個長度,容量為5的slice
func main() {
s := make([]int, 2, 5)
fmt.Println("len: ", len(s))
fmt.Println("cap: ", cap(s))
s = append(s, 2)
fmt.Println("--------")
fmt.Println("len: ", len(s))
fmt.Println("cap: ", cap(s))
fmt.Println("--------")
s[0] = 12
for _, v := range s {
fmt.Println(v)
}
}複製程式碼
初始化slice後,他的長度為2,也就是元素個數為2,因為我們沒有給任何一個元素賦值,所以為int的zero值,也就是0.可以用len和cap看一下這個slice的長度和容量。
用append給這個slice新增新值,返回一個新的slice,如果容量不夠時,go會自動增加容易量,小於一1000個長度時成倍的增長,大於1000個長度時會以1.25或者25%的位數增長。
上面的程式碼執行完後,slice的結構如下
Slice 的宣告和初始化
建立和初始化一個slice有幾種不同的方式,下面我會一一介紹
使用make宣告一個slice
slice1 := make([]int, 3)
fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)
slice1 = append(slice1, 1)
fmt.Println("len: ", len(slice1), "cap: ", cap(slice1), "array :", slice1)複製程式碼
make([]int, 3) 宣告瞭個長度為3的slice,容量也是3。下面的append方法會新增一個新元素到slice裡,長度和容量都會發生變化。
輸出結果:
len: 3 cap: 3 array : [0 0 0]
len: 4 cap: 6 array : [0 0 0 1]複製程式碼
也可以通過過載方法指定slice的容量,下面:
slice2 := make([]int, 3, 7)
fmt.Println("len: ", len(slice2), "cap: ", cap(slice2), "array :", slice2)複製程式碼
輸出長度為3,容量為7
使用slice字變數
使用字變數來建立Slice,有點像建立一個Array,但是不需要在[]指定長度,這也是Slice和Array的區別。Slice根據初始化的資料來計算度和容量
建立一個長度和容量為5的Slice
slice3 := []int{1, 2, 3, 4, 5}
fmt.Println("len: ", len(slice3), "cap: ", cap(slice3), "array :", slice3)複製程式碼
也可以通過索引來指定Slice的長度和容量,
下面建立了一個長度和容量為6的slice
slice4 := []int{5: 0}
fmt.Println("len: ", len(slice4), "cap: ", cap(slice4), "array :", slice4)複製程式碼
宣告nil Slice,內部結構的指標為nil。可以用append給slice填加新的元素,內部的指標指向一個新的陣列
var slice5 []int
fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)
slice5 = append(slice5, 4)
fmt.Println("len: ", len(slice5), "cap: ", cap(slice5), "array :", slice5)複製程式碼
建立空Slice有兩種方式
slice6 := []int{}
fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
slice6 = append(slice6, 2)
fmt.Println("len: ", len(slice6), "cap: ", cap(slice6), "array :", slice6)
slice7 := make([]int, 0)
fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)
slice7 = append(slice7, 7)
fmt.Println("len: ", len(slice7), "cap: ", cap(slice7), "array :", slice7)複製程式碼
slice的切片
// 建立一個容量和長度均為6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 對slices1進行切片,長度為2容量為4
slice2 := slice1[1:3]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)複製程式碼
slice1的底層是一個容量為6的陣列,slice2指底層指向slice1的底層陣列,但起始位置為array的第一個元素也就是23.因為slices2從索引1開始的,所以無法訪問底層陣列索引1之前的元素,也無法訪問容量之後的元素。可以看下圖理解一下。
新建立的切片長度和容量的計算
對於一個新slice[x:y] 底層陣列容量為z,
x: 新切片開始的元素的索引位置,上面的slice1[1:3]中的1就是起始索引
y:新切片希望包含的元素個數,上面的slice1[1:3],希望包含2個底層陣列的元素 1+2=3
容量: z-x 上面的slice1[1:3] 底層陣列的容量為6, 6-1=5所以新切片的容量為5
修改切片導致的後果
由於新建立的slice2和slice1底層是同一個陣列,所以修改任何一個,兩個slice共同的指向元素,會導致同時修改的問題
// 建立一個容量和長度均為6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 對slices1進行切片,長度為2容量為4
slice2 := slice1[1:3]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)
//修改一個共同指向的元素
//兩個slice的值都會修改
slice2[0] = 11111
fmt.Println("slice1", slice1)
fmt.Println("slice2", slice2)複製程式碼
如圖所示
需注意的是,slice只能訪問其長度範圍內的元素,如果超出長度會報錯。
除了修改共同指向元素外,如果新建立的切片長度小於容量,新增元素也會導致原來元素的變動。slice增加新元素使用內建的方法append。append方法會建立一個新切片。
// 建立一個容量和長度均為6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 對slices1進行切片,長度為2容量為4
slice2 := slice1[1:3]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)
//修改一個共同指向的元素
//兩個slice的值都會修改
slice2[0] = 11111
fmt.Println("slice1", slice1)
fmt.Println("slice2", slice2)
// 增加一個元素
slice2 = append(slice2, 55555)
fmt.Println(slice1)
fmt.Println(slice2)複製程式碼
如果切片的容量足夠就把新元素合新增到切片的長度。如果底層的的陣列容量不夠時,會重新建立一個新的陣列並把現有元素複製過去。
slice3 := []int{1, 2, 3}
fmt.Println("slice2 cap", cap(slice3))
slice3 = append(slice3, 5)
fmt.Println("slice2 cap", cap(slice3))複製程式碼
輸出的結果為:
slice2 cap 3
slice2 cap 6複製程式碼
容量增長了一倍。
控制新建立slice的容量
建立一個新的slice的時候可以限制他的容量
// 建立一個容量和長度均為6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 對slices1進行切片,長度為2容量為3
slice2 := slice1[1:3:4]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)複製程式碼
slice2長度為2容量為3,這也是通過上面的公式算出來的
長度:y-x 3-1=2
容量:z-x 4-1= 3
需要注意的是容量的長度不能大於底層陣列的容量
綠顏色表示slice2中的元素,黃顏色表示容量中示使用的元素。但是需要注意的是,我們修改或者增加slice2容量範圍內的元素個數依然會修改slice1。
// 建立一個容量和長度均為6的slice
slice1 := []int{5, 23, 10, 2, 61, 33}
// 對slices1進行切片,長度為2容量為3
slice2 := slice1[1:3:4]
fmt.Println("cap", cap(slice2))
fmt.Println("slice2", slice2)
//修改一個共同指向的元素
//兩個slice的值都會修改
slice2[0] = 11111
fmt.Println("slice1", slice1)
fmt.Println("slice2", slice2)
// 增加一個元素
slice2 = append(slice2, 55555)
fmt.Println(slice1)
fmt.Println(slice2)複製程式碼