1. 程式人生 > >Golang學習筆記--Array和Slice

Golang學習筆記--Array和Slice

目錄

 

Reference

Array

Slice

slice的建立

Slice常用操作

reslice

append函式

copy函式

range遍歷


Reference

https://blog.golang.org/go-slices-usage-and-internals

Array

  1. 陣列是值型別,賦值和傳參會複製整個陣列,而不是指標。
  2. 陣列⻓長度必須是常量,且是型別的組成部分。[2]int 和 [3]int 是不同型別。
  3. ⽀支援 "=="、"!=" 操作符,因為記憶體總是被初始化過的。
  4. 指標陣列 [n]*T,陣列指標 *[n]T。

Slice

因為array是值型別,賦值和傳參行為使用array都會發生資料拷貝,如果操作物件是比較大的array,那麼時間和空間上的開銷都會成為問題。

  7 func arrayAsParamTest() {
  8         fmt.Println("=========SliceTest.sliceAsParamTest()==========")
  9         arrayA := [2]int{1, 2}
 10         var arrayB [2]int
 11         arrayB = arrayA
 12         fmt.Printf("Address: %p.\n", &arrayA)
 13         fmt.Printf("Address: %p.\n", &arrayB)
 14         arrayAsParam(arrayA)
 15 }
=========SliceTest.sliceAsParamTest()==========
Address: 0xc420014390.
Address: 0xc4200143a0.
Address: 0xc4200143b0.

傳參時用陣列指標可以避免資料拷貝,但是如果原陣列指標指向了新的陣列,那麼函式裡面的指標也會隨之變化,很容易引入bug。

slice是建立在陣列基礎之上的一種資料型別。slice本身只儲存元資料,使用者資料儲存在底層array,slice用一個指標指向底層陣列(可以指向底層陣列任意元素,指向的元素也就是slice的首元素),並用length限定slice的讀寫區域,用capacity表示slice的容量,capacity減去length就是slice的剩餘儲存空間。

slice的建立

建立slice有以下幾種用法:

1.通過make函式。make([]type, length, capacity),make函式在堆記憶體上分配底層陣列。

2.用[]對array或者slice執行切片操作得到一個新的slice。

3.用字面值初始化一個slice。

4.建立一個nil slice,它的值就等於nil。

func slice() {
         fmt.Println("=========SliceTest.slice()========")
         //1
         a := make([]int, 5) //len = 5, cap = 5
         b := make([]int, 0, 5) //len = 0, cap = 5
         b1 := make([]int, 0)  //empty slice
 
         //2
         c := b[:2] //len = 2 , cap = 5
         d := c[2:5] //len = 3, cap = 3
     
         //3
         e := [3]bool{true, false, true}
         e1 := []bool{}  //empty slice

         //4, nil slice
         var f []int
}

上面demo中有兩種特殊的slice,empty slice和nil slice,其區別在於:empty slice指向的地址不是nil,指向的是一個記憶體地址,但是它沒有分配任何記憶體空間,即底層元素包含0個元素。函式發生異常,需要返回slice,這種情況通常返回的是nil slice。如果是查詢函式,沒有查詢到結果,那麼返回的通常是empty slice。

因此,在處理這類函式呼叫的結果時,經常先通過nil(if result == nil)判斷函式執行是否出錯,返回結果不等於nil再進行處理。

Slice常用操作

reslice

reslice其實就是在現有slice物件的基礎上進行切片來建立新的slice物件,以便在capacity允許的範圍內調整屬性,並且共享底層陣列。

append函式

我們可以通過append函式向slice尾部新增新的元素,有兩種基本情況:

1.新增元素後slice的length不超出capacity,將對原底層陣列的資料進行修改,如果該陣列有多個切片,需要注意這種相互影響。

2.新增元素後slice的length超出capacity,將會申請新的底層陣列並拷貝資料。

函式原型如下:

func append(s []T, vs ...T) []T

func printSlice(s string, x []int) {
        fmt.Printf("%s len=%d cap=%d adress=%p value=%v\n", s, len(x), cap(x), &x, x)
}
func SliceAppend() {
        fmt.Println("**********Test - Test append*********")
        var s []int
        printSlice("s", s)

        // append works on nil slices.
        s = append(s, 0)
        printSlice("s", s)

        // The slice grows as needed.
        s = append(s, 1)
        printSlice("s", s)

        // We can add more than one element at a time.
        s = append(s, 2)
        printSlice("s", s)

        s = append(s, 3, 4)
        printSlice("s", s)

        s = append(s, 5, 6)
        printSlice("s", s)
}
**********Test - Test append*********
s len=0 cap=0 adress=0xc00000a320 value=[]
s len=1 cap=1 adress=0xc00000a340 value=[0]
s len=2 cap=2 adress=0xc00000a380 value=[0 1]
s len=3 cap=4 adress=0xc00000a3c0 value=[0 1 2]
s len=5 cap=8 adress=0xc00000a400 value=[0 1 2 3 4]
s len=7 cap=8 adress=0xc00000a440 value=[0 1 2 3 4 5 6]

從上面的demo中我們可以發現,用append函式向slice壓入新的元素時,如果slice底層的陣列空間不足,將會分配一個新的陣列,然後讓slice的指標指向這個新的陣列,capacity也修改為新陣列的長度。看起來每次重新分配陣列的時候,在滿足slice的長度需求的同時,size都是之前的2倍,也就是會預留一部分空間。但是,下面我們稍微修改一下SliceAppend函式,value分別為2,3,4的三個元素一次append到slice:

func SliceAppend() {
        fmt.Println("**********Test - Test append*********")
        var s []int
        printSlice("s", s)

        // append works on nil slices.
        s = append(s, 0)
        printSlice("s", s)

        // The slice grows as needed.
        s = append(s, 1)
        printSlice("s", s)

        // We can add more than one element at a time.
        s = append(s, 2, 3, 4)
        printSlice("s", s)

        s = append(s, 5, 6)
        printSlice("s", s)
}
**********Test - Test append*********
s len=0 cap=0 adress=0xc00000a320 value=[]
s len=1 cap=1 adress=0xc00000a340 value=[0]
s len=2 cap=2 adress=0xc00000a380 value=[0 1]
s len=5 cap=6 adress=0xc00000a3c0 value=[0 1 2 3 4]
s len=7 cap=12 adress=0xc00000a400 value=[0 1 2 3 4 5 6]

在slice的元素個數為5時,重新分配的陣列的size不再是8,而是6了;slice元素個數為7時,cap不再是8,而是12。相比第一份程式碼,僅僅是將中間兩次append操作合併到一次而已,看來底層陣列的分配演算法並沒有我們前面想象的那麼簡單。

那麼在slice擴容時,底層陣列的size到底是怎樣去分配的呢?擴容策略如下:

1.如果新的size超出現有size的2倍,分配的大小就是新的size,如果新size是奇數還會加1(size為1是例外)。

2.否則;如果當前size小於1024,按每次2倍size分配空間,否則每次按當前size的四分之一擴容。

copy函式

 30 func sliceCopy() {
 31         fmt.Println("=========SliceTest.sliceCopy()==========")
 32         s := []string{"hunk", "jack", "bob"}
 33         d := make([]string, 3, 3)
 34         copy(d, s)
 35         fmt.Printf("s len=%d cap=%d adress=%p value=%v\n", len(s), cap(s), &s, s)
 36         fmt.Printf("d len=%d cap=%d adress=%p value=%v\n", len(d), cap(d), &d, d)
 37         for i, v := range d {
 38                 fmt.Printf("d[%d] is %s\n", i, v)
 39         }
 40 }
=========SliceTest.sliceCopy()==========
s len=3 cap=3 adress=0xc42000a440 value=[hunk jack bob]
d len=3 cap=3 adress=0xc42000a460 value=[hunk jack bob]
d[0] is hunk
d[1] is jack
d[2] is bob

copy函式會返回實際拷貝的元素個數,這也取決於源和目標兩者長度較短的那一個。

range遍歷

slice既然也是一組資料的集合,必然支援range對其進行遍歷。

 46 /*
 47 When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.
 48 */
 49 func sliceRange() {
 50         fmt.Println("=========SliceTest.sliceRange()=========")
 51         var pow = []int{1, 2, 4, 8}
 52 
 53         for i, v := range pow {
 54                 fmt.Printf("value = %d, value.address = %x, slice-address = %x.\n", v, &v, &pow[i])
 55         }
 56 
 57         for i := range pow {
 58                 fmt.Printf("%d\n", i)
 59         }
 60 
 61         for _, v := range pow {
 62                 fmt.Printf("%d\n", v)
 63         }
 64 }
=========SliceTest.sliceRange()=========
value = 1, value.address = c4200142e8, slice-address = c4200120c0.
value = 2, value.address = c4200142e8, slice-address = c4200120c8.
value = 4, value.address = c4200142e8, slice-address = c4200120d0.
value = 8, value.address = c4200142e8, slice-address = c4200120d8.

用range對slice進行遍歷時,每次返回index和value兩個資料,可以根據需要省略取值。需要注意的是,value是值拷貝的結果,並且每次的value都用了同一塊儲存區域,所以range遍歷時修改返回的value無法修改到底層陣列的資料,可以用slice[index]對其修改。