1. 程式人生 > >初識go語言之 陣列與切片(建立,遍歷,刪除,插入,複製)

初識go語言之 陣列與切片(建立,遍歷,刪除,插入,複製)

1、陣列

go語言的陣列與其他語言的資料定義基本類似:大小確定,記憶體連續,可以隨機訪問。陣列的元素使用操作符[]來索引。首元素是array[0],最後一個元素是array[len(array)-1]。

1.1陣列的建立

陣列的建立有下面三種方式

[length]Type
[N]Type{value1, value2, ..., valueN}
[...]Type{value1, value2, ..., valueN}

對於陣列而言,它的長度和容量的大小是相等的,也就是len(array)和cap(array)的返回值相同,對於最後一種不指定大小的情況下,程式會自動計算陣列的大小。所有情況下,陣列在建立後大小不能被改變

var array1 [5]int
var array2 [2][2]int
array1[0], array1[1], array1[2], array1[3] = 4, 2, 1, 6
array2[0][0], array2[0][1] = 2, 1

array3 := [3]int{1, 3, 5}
array4 := [...]int{4, 1, 5, 6, 7}
fmt.Printf("array1 size[%d] cap[%d]\n", len(array1), cap(array1))
fmt.Printf("array2 size[%d] cap[%d]\n", len(array2), cap(array2))
fmt.Printf("array3 size[%d] cap[%d]\n", len(array3), cap(array3))
fmt.Printf("array4 size[%d] cap[%d]\n", len(array4), cap(array4))

//output
array1 size[5] cap[5]
array2 size[2] cap[2]
array3 size[3] cap[3]
array4 size[5] cap[5]

1.2 陣列的遍歷

陣列的遍歷有兩種方式:

array := [5]int{1, 3, 5}
//方式一、
for i := 0; i < len(array); i++{
    fmt.Printf("index array[%d] = %d\n", i, array[i])
}

//方式二、
for index, val := range array {
    fmt.Printf("index array[%d] = %d\n", index, val)
}

//output
index array[0] = 1
index array[1] = 3
index array[2] = 5
index array[3] = 0
index array[4] = 0

1.3陣列使用注意點

與C/C++不同,go語言陣列的工作方式有個重要的差異

1)陣列是值,將一個數組賦值給另一個數組,會拷貝所有的元素

2)陣列作為函式引數,收到的是陣列的一個拷貝,而不是它的指標

3)陣列的大小是型別的一部分,[10]int和[20]int是不一樣的

針對第二種,這裡特別說一下,你可以用類似於C的方式,將陣列的指標作為函式引數傳入來防止拷貝,但這不符合go語言的書寫習慣,很不go。在go語言中我們使用切片

2、切片(slice)

一個slice是一個數組的部分引用,其底層還是一個數組。在記憶體中它是一個包含三個域的結構體:指向陣列的指標,切片的長度,切片的容量。如下圖所示:

引入切片主要是為了消除陣列的固定長度的限制,使用者可以不需要手動指定大小,切片會自動調整自己的大小。將切片和陣列放在一起講主要是因為他們的很多共性是一致的,但又存在各自的特性。下面我們來細說切片

2.1 切片的建立

切片有下面四種建立方式

make([]type, length, capacity)
make([]type, length)
[]type{}
[]type{value1, value2,..., valueN}

通過第3、4兩種建立方式我們可以知道,陣列和切片的區別就是是否指定了大小,

slice1 := make([]int, 4, 6)
fmt.Printf("slice1 addr %p size[%d] cap[%d]\n", slice1, len(slice1), cap(slice1))

slice2 := make([]int, 4)
fmt.Printf("slice2 addr %p size[%d] cap[%d]\n", slice2, len(slice2), cap(slice2))

slice3 := []int{}
fmt.Printf("slice3 addr %p size[%d] cap[%d]\n", slice3, len(slice3), cap(slice3))

slice4 := []int{1, 2}
fmt.Printf("slice4 addr %p size[%d] cap[%d]\n", slice4, len(slice4), cap(slice4))

//output
slice1 addr 0xc04200c600 size[4] cap[6]
slice2 addr 0xc042008560 size[4] cap[4]
slice3 addr 0x55c988 size[0] cap[0]
slice4 addr 0xc04200a240 size[2] cap[2]

slice3是一個空切片。第二種方式沒有指定capacity的值,其預設值就是length的值

2.2 切片的操作

s[n]             切片s中索引位置為n的項
s[n:m]           從切片s的索引位置n到m-1處所獲得的切片
s[n:]            從切片s的索引位置到len(s)-1處所獲得的切片
s[:m]            從切片s的索引位置0到m-1處所獲得的切片
s[:]             從切片s的索引位置0到len(s)-1處獲得的切片

不管是哪一種操作,新獲得的切片跟源切片底層對應的陣列都是同一個,所以對其中一個切片元素的修改也會影響到另外一個

slice := []int{10, 20, 30, 40, 50}
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

newSlince := slice[1:3]
newSlice[0] = 999
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)


slice addr:0xc042072090 len:5 cap:5 slice:[10 20 30 40 50]
slice addr:0xc042072090 len:5 cap:5 slice:[10 999 30 40 50]

如下圖所示:

2.3 切片的遍歷

切片的遍歷跟陣列類似,兩種遍歷方式

//方式一、
for i := 0; i < len(slice); i++{
    fmt.Printf("index slice[%d] = %d\n", i, slice[i])
}

//方式二、
for index, val := range slice {
    fmt.Printf("index slice[%d] = %d\n", slice, val)
}

2.4 修改切片元素值

切片底層還是一個數組,所以修改切片的元素可以直接通過下標索引的方式來操作。

slice := []int{1, 2, 3, 100}
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

slice[1] = 101
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

//output
slice addr:0xc042008540 len:4 cap:4 slice:[1 2 3 100]
slice addr:0xc042008540 len:4 cap:4 slice:[1 101 3 100]

2.5切片的追加

往切片中追加元素可以使用內建的append函式,需要傳入一個slice。append函式可以有兩種模式

1)一次性追加多個元素。append(slice, value1, value2,..., valueN)

2)追加單個切片,並以...結尾。append(slice, append_slice...)

slice := make([]int, 2, 4)
append_slice := []int{1, 2}
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

slice = append(slice, append_slice...)
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

slice = append(slice, 10, 20)
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

slice = append(slice, 30, 40, 50)
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

//output
slice addr:0xc0420540a0 len:2 cap:4 slice:[0 0]
slice addr:0xc0420540a0 len:4 cap:4 slice:[0 0 1 2]
slice addr:0xc04207a080 len:6 cap:8 slice:[0 0 1 2 10 20]
slice addr:0xc04208a000 len:9 cap:16 slice:[0 0 1 2 10 20 30 40 50]
  • 1,2可以看出當append後的length沒有大於capacity時,切片指向的底層陣列地址並沒有改變。
  • 2,3可以看出當lenght大於capacity後,切片指向的底層陣列地址已經改變,並且capacity擴大了一倍。這是因為底層會重新new出一個數組,並把append後的資料全部copy過來導致的。這有點c++STL裡面的vector的擴充機制。所以使用時這裡也會存在一定的優化,如題能夠提前預估出大小,可以減少記憶體拷貝的次數

這裡需要明確一點的是,切片的大小改變需要通過append來擴充,而不能通過給一個大於capacity-1索引賦值來擴充,如下所示:

new_slice := make([]int, 2, 3)
new_slice[3] = 1 //runtime error: index out of range

2.6切片的插入和刪除

切片的增加主要還是通過append函式來完成的,總體上就是將切片拆分,拼接再組合的過程。下面分別舉例說明

2.6.1 刪除

刪除下標為index的元素

slice = append(slice[:index], slice[index+1:]...)

例項如下:

slice := []int{1, 3, 4, 6, 7, 9}
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)
//刪除下標為2的元素
slice = append(slice[:2], slice[3:]...)
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

//output
slice addr:0xc04200c540 len:6 cap:6 slice:[1 3 4 6 7 9]
slice addr:0xc04200c540 len:5 cap:6 slice:[1 3 6 7 9]

同理,刪除index1~index2(不包含index2)之間的元素可以這樣操作:

slice = append(slice[:index1], slice[index2:]...)

2.6.2 插入

append是在切片的尾部追加元素。有時候我們需要在切片的指定位置插入一個元素,或者一組元素。具體如下:

//在index=2的位置插入一個值為100的元素
slice := []int{1, 3, 4, 6, 7, 9}
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)
//將index後面的資料儲存到一個臨時的切片(其實是對index後面資料的複製)
tmp := append([]int{}, slice[2:]...)

//拼接插入的元素
slice = append(slice[0:2], 100)

//與臨時切片再組合得到最終的需要的切片
slice = append(slice, tmp...)
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

//output
slice addr:0xc042072090 len:6 cap:6 slice:[1 3 4 6 7 9]
slice addr:0xc042048060 len:7 cap:12 slice:[1 3 100 4 6 7 9]

2.7切片的複製

在go語言中,一個切片賦值給另一個切片時,它們指向的底層的陣列是一樣的。有時候我們需要快取切片的資料(就好比上面插入操作的第一步),就需要複製操作。前面提到了這麼多,我們可以通過遍歷賦值的方式也很容易實現切片的賦值。亦或者用append函式,通過往一個空切片追加當前切片的方式實現複製:

slice := []int{1,3,6}
copy_slice := append([]int{},slice[:]...)

在go語言,我們更推薦使用內建的copy函式。

func copy(dst, src []T) int

copy函式支援兩個引數,第一個是目標切片,第二個是源切片,將源切片複製到目標切片上。複製的大小取dst和src中長度最小值(min(len(dst), len(src)))

slice := []int{1, 3, 4, 6, 7, 9}
fmt.Printf("slice addr:%p len:%d cap:%d slice:%v\n", slice, len(slice), cap(slice), slice)

copy_slice := make([]int, 3, len(slice))
copy(copy_slice, slice)

fmt.Printf("copy_slice addr:%p len:%d cap:%d slice:%v\n", copy_slice, len(copy_slice), cap(copy_slice), copy_slice)

//output
slice addr:0xc042072090 len:6 cap:6 slice:[1 3 4 6 7 9]
copy_slice addr:0xc0420720c0 len:3 cap:6 slice:[1 3 4]

如果想實現對slice的完整複製,只需要建立一個length為len(slice)大小的切片,再copy就可以了

slice := []int{1, 3, 4, 6, 7, 9}
copy_slice := make([]int, len(slice), len(slice))
copy(copy_slice, slice)

特別的當目標的length為0時,複製操作不會進行任何操作

slice := []int{1, 3, 4, 6, 7, 9}
copy_slice := make([]int, 0, len(slice))
copy(copy_slice, slice)
fmt.Printf("copy_slice addr:%p len:%d cap:%d slice:%v\n", copy_slice, len(copy_slice), cap(copy_slice), copy_slice)

//output
copy_slice addr:0xc0420720c0 len:0 cap:6 slice:[]