1. 程式人生 > >Go 靈活多變的切片Slice

Go 靈活多變的切片Slice

  我們知道陣列定義好之後其長度就無法再修改,但是,在實際開發過程中,有時候我們並不知道需要多大的陣列,我們期望陣列的長度是可變的,

在 Go 中有一種資料結構切片(Slice) 解決了這個問題,它是可變長的,可以隨時向Slice 裡面新增資料。

1 什麼是切片(Slice)

   在 Go 原始碼中是這樣定義切片的,原始碼地址:https://github.com/golang/go/blob/master/src/runtime/slice.go

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

  從原始碼中我們可以看到 Slice 也是一種結構體,這個結構體的名字是:Slice,這個結構體包含三個屬性:array、len、cap。

  第1個屬性是指向底層陣列的指標(Pointer),指向陣列中 Slice 開始的位置;

  第2個屬性是切片中元素的個數(len),也就是這個 Slice 的長度;

  第3個屬性是這個切片的容量(cap),也就是 Slice 從開始位置到底層陣列最後位置的長度;

2 切片的建立

   2.1 切片的建立方式有很多種,一個比較通用的建立方式,使用 Go 的內建函式 make() 建立

package main

import "fmt"

func main() {
    var s1 []int = make([]int,5,8)
    var s2 []int = make([]int,8)

    fmt.Println(s1, s2)
}

輸出結果

[[email protected]_81_181_centos golang]# go run slice01.go 
[0 0 0 0 0] [0 0 0 0 0 0 0 0]
[[email protected]_81_181_centos golang]#  

   make() 函式建立切片,需要提供三個引數,切片的型別、切片的長度、切片的容量。其中第3個引數是可選的,如果第三個引數不提供的話,

則代表建立的是滿容切片,也就是長度和容量相等。另外切片也可以通過型別自動推導,省去型別定義和 var 關鍵字。比如:

package main

import "fmt"

func main() {
    var s1 []int = make([]int, 5, 8)
    s2 := make([]int, 8)

    fmt.Println(s1, s2)
}

  另外,我們可以使用 len()、cap() 函式獲取切片的長度和容量

package main

import "fmt"

func main() {
   numbers := make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int) {
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x), cap(x),x)
}  

輸出結果

[[email protected]_81_181_centos golang]# go run slice01.go 
len=3 cap=5 slice=[0 0 0]
[[email protected]_81_181_centos golang]# 

  2.2 用已有的陣列生成切片

package main

import "fmt"

func main() {
    // 1.通過陣列生成切片
    // 定義一個數組
    arr1 := [8]int{1,2,3,4,5,6,7,8}
    fmt.Println(arr1)
   
    // 定義一個切片
    s1 := arr1[1:4]
    fmt.Println(s1)
}

輸出結果

[[email protected]_81_181_centos golang]# go run slice01.go 
[1 2 3 4 5 6 7 8]
[2 3 4]
[[email protected]_81_181_centos golang]#  

 2.3 用已有的切片生成切片

package main

import "fmt"

func main() {
   s := []int{1,2,3}
   s1 := s[1:3]  // s1 為 [2,3]
   s2 := s1[1:2]
   fmt.Println(s2)
}

輸出結果

[[email protected]_81_181_centos golang]# go run slice01.go 
[3]
[[email protected]_81_181_centos golang]# 

3 切片的初始化

   使用 make() 函式建立的切片是零值切片,Go 語言還提供了另外一種建立切片的方法,允許我們給它賦初值,使用這種方式建立的切片

是滿容的。

  3.1 通過陣列初始化切片:

s := []int{1,2,3,4,5}

   直接初始化切片s

s := arr[:]

 初始化切片s,是陣列 arr 的引用

s := arr[startIndex:endIndex]

 從已有陣列中建立一個新的切片,新切片元素是從陣列 arr 下標 startIndex 到 endIndex-1

s := [startIndex:]

 預設 endIndex 表示一直取到陣列的最後一個元素

s :=arr[:endIndex]

 預設 startIndex 表示從陣列的第一個元素開始取值

3.2 通過切片初始化切片

s1 := s[startIndex:endIndex]

4 切片的遍歷

   切片在遍歷上的方式和陣列是一樣的,支援 for 和 使用 range 關鍵字遍歷

package main

import "fmt"

func main() {
   s := []int{1,2,3,4,5}

   // for 遍歷
   for i := 0;i < len(s);i++ {
       fmt.Println(s[i])
   }

   // 使用 range 關鍵字
   for index,value := range s {
       fmt.Println(index, value)
   }
}

輸出結果

[[email protected]_81_181_centos golang]# go run slice01.go 
1
2
3
4
5
0 1
1 2
2 3
3 4
4 5
[[email protected]_81_181_centos golang]#  

5 切片的擴容(追加)
   我們知道切片的長度是可變化的,這個可變其實就是追加操作(append)導致的,我們使用 append 操作追加元素到切片時,如果容

量不夠,則會建立新的切片,意味著記憶體地址也會發生變化,如果底層陣列沒有擴容,那麼追加前後的兩個切片共享底層陣列,當底

層陣列是共享的,一個切片的內容變化會影響到另一個切片的內容。如果底層陣列擴容了,那麼追加前後的兩個切片是不共享底層陣列

的。

package main

import "fmt"

func main() {
  // 定義切片s1 且切片s1是滿容的
  s1 := []int{1,2,3,4,5}
  // 列印切片s1
  fmt.Printf("記憶體地址:%p \t\t長度:%v \t\t容量%v \t\t包含的元素:%v\n",s1,len(s1),cap(s1),s1)

  // 對滿容的切片追加元素
  s2 := append(s1,6)
  // 列印切片s2
  fmt.Printf("記憶體地址:%p \t\t長度:%v \t\t容量%v \t\t包含的元素:%v\n",s2,len(s2),cap(s2),s2)
  // 修改s2的值
  s2[1] = 10
  fmt.Println(s1,s2)

  // 對沒有滿容的切片追加元素
  s3 := append(s2,7)
  // 列印切片s3
  fmt.Printf("記憶體地址:%p \t\t長度:%v \t\t容量%v \t\t包含的元素:%v\n",s3,len(s3),cap(s3),s3)
  // 修改s3的值
  s3[1] = 20
  fmt.Println(s2,s3)
}

輸出結果

[[email protected]_81_181_centos golang]# go run slice01.go 
記憶體地址:0xc420010150 		長度:5 		容量5 		包含的元素:[1 2 3 4 5]
記憶體地址:0xc420042050 		長度:6 		容量10 		包含的元素:[1 2 3 4 5 6]
[1 2 3 4 5] [1 10 3 4 5 6]
記憶體地址:0xc420042050 		長度:7 		容量10 		包含的元素:[1 10 3 4 5 6 7]
[1 20 3 4 5 6] [1 20 3 4 5 6 7]
[[email protected]_81_181_centos golang]# 

    我們可以看到輸出結果中 s1、s2 的記憶體地址發生了變化,是因為我們將元素 6 追加至切片 s1 中,超過了切片的最大容量 5 ,會創

建一個新的切片並將 s1 原有的元素拷貝一份至新的切片中,並且我們修改 s2 的值,發現 s1 並沒有發生變化,說明s1、s2不在共享底

層陣列。

   擴容後的切片 s2 容量為 10 ,我們再向 s2 追加元素 7 後,沒有超過 s2 的最大容量,且s2、s3 的記憶體地址一致,並且修改 s3 的值,

s2也會發生變化,說明s2、s3 共享底層陣列。

   所以,初始化切片的時候給出了足夠的容量,append 操作的時候不會建立新的切片。

   這裡可能還會有一個疑問,為什麼追加一個元素後容量由原來的 5 變成了 10?這裡牽涉到 Slice 的擴容機制,可以參考這篇文章寫得非

常詳細:http://blog.realjf.com/archives/217