iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
自我挑戰組

跟著 Go 實戰聖經 一起自學 Go系列 第 15

DAY 15 Go 語言 的複合型別 - 切片 (slice) 的內部運作及隱藏陣列

  • 分享至 

  • xImage
  •  

昨天瞭解了切片的使用方式,但光會使用而不了解切片的原理,絕對會在未來吃一些不明所以的虧,所以我們立馬來了解一下切片到底從何而來以及怎麼運作!

切片 (slice) 的內部運作

根據前幾天的學習,我們知道陣列是一種資料型別,而正常有型別的資料是 可以被複製 以及 可以被比較 ,但複製出來的陣列跟本來的陣列本質上是不一樣的。切片因為是以陣列為核心所衍發出來的,所以有些微的不同: 切片不能被複製 ,切片其實有點類似指標,每個切片他都有指向一個底層的隱藏陣列,而資料事實上是存在這個隱藏陣列中的,那為什麼這個陣列可以自由決定長度呢?

其實是因為切片有以下三項屬性,而我們稱這三個屬性為底層隱藏陣列的 窗格 (window)

1. 指向底層陣列起始位置的指標 (pointer)

切片自帶手指頭,當你呼叫一個切片,他會指向底層陣列的起始位置,這就是我們平常找到切片的原理。

舉個例子:今天我在捷運松山站,要去捷運小巨蛋站,於是我呼叫松山新店線[2],如下圖,黃色箭頭指引我松山新店線底層陣列的起始位置,這下要找到索引二(捷運小巨蛋站)絕對是一塊小蛋糕。
松山新店線捷運圖

2. 切片的長度 (length)

切片的長度就是此切片內現有的元素數量,我們一樣可以使用 len() 函式來查詢切片的長度。

3. 切片的容量 (capacity)

切片的容量其實就是切片之所以可以伸縮自如的秘訣,也就是這個切片總共可以容納的空間,今天當你 append() 一個值到切片中時,第一時間會先確認此切片是否有可容納空間,有的話就會把值丟進這個切片身後的隱藏陣列; 沒有的話首先隱藏陣列會先找一個更大的位置安頓,先產生一個新的容量更大的陣列,然後將舊的值複製過去,再加上這次 append() 的值,當然同時切片的指標也會改指為新隱藏陣列的起始位置,若我們想知道此切片的容量,使用內建函式 cap() 即可查詢容量長度。

舉個例子:今天我和朋友A一起搭捷運,朋友B也加入了,因為還有位置朋友B就依序做下去(如下圖)
捷運位置範例示意圖

但我這個人朋友比較多,朋友C、朋友D也依序上車了,我們本來位置的容量就不夠了,所以我們就換到更長的位置依序坐下(如下圖),而切片的指標也會從原本的位置,改成指向新位置的起始位置,這就是容量的概念。
捷運位置範例示意圖

使用 make() 定義一個初始化的切片

有時候我們想要控制切片的長度或容量,這時可以用內建函式 make() 來定義一個初始化的切片

<新的初始切片> = make(<切片型別>, <長度>, [<容量>])

補充:
make() 函式的長度參數為必填,而容量若省略不填的話就會預設是長度,而這個新切片裡元素的值皆為零值。
若一開始就肯定切片的容量,製作一個固定容量的切片是可以提升效能,避免隱藏陣列還要找更大位置。

範例 1:

package main

import (
	"fmt"
)

func main() { 
    var slice1 []string // 用 var 建立切片 slice1
    slice2 := make([]bool,5) // 用 make() 函式建立 bool 型別,長度為 5 的切片(沒設容量容量預設跟長度一樣為 5 )
    slice3 := make([]int,5,10) // 用 make() 函式建立 int 型別,長度為 5 ,容量為 10 的切片

	fmt.Println("slice1:",slice1," len:",len(slice1)," cap:",cap(slice1))
    fmt.Println("slice2:",slice2," len:",len(slice2)," cap:",cap(slice2))
    fmt.Println("slice3:",slice3," len:",len(slice3)," cap:",cap(slice3))
}

範例 1(執行結果):

slice1: []  len: 0  cap: 0
slice2: [false false false false false]  len: 5  cap: 5
slice3: [0 0 0 0 0]  len: 5  cap: 10

切片隱藏陣列值改變的影響

我們了解了每個切片實際上比較像是指標,指向背後的幕後黑手 隱藏陣列 ,且實際值也是存在隱藏陣列中後,還有很重要的兩個觀念需要瞭解清楚

1. 一個隱藏陣列可能同時被多個切片指向

正常從某切片為基礎,新建出來的切片都是 指向同一個底層陣列 ,只是這些切片各自的長度可能不盡相同,但特別注意的是,當今天你更換某切片的元素值,可能會 連同指向同一個底層陣列的其他切片元素值一同被更改 ,這就是前面說到的沒了解原理而可能吃一些不明所以的虧。

2. 當切片擴充到超過本來隱藏陣列容量時,隱藏陣列會搬家,代表指向的地方會不同

雖然可能有很多切片指向同一個底層隱藏陣列,但今天當對其中某個切片新增元素值,而超過本來隱藏陣列容量時,隱藏陣列除了換更大的位置,切片也會指向新隱藏陣列的起始位置,這代表著 新增超過本來容量元素值的切片,指向的隱藏陣列已經和本來隱藏陣列不相同了 ,而這個行為我們稱為 陣列置換
陣列置換

今天介紹了切片的內部運作及隱藏陣列的存在,明天會使用各種方式來複製切片,來看看用不同的複製方式與隱藏陣列的關聯,那我們明天見~


上一篇
DAY 14 Go 語言 的複合型別 - 切片 (slice) 使用方式
下一篇
DAY 16 Go 語言 的複合型別 - 切片 (slice) 不同的複製方式與隱藏陣列的關聯
系列文
跟著 Go 實戰聖經 一起自學 Go30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言