面試題
最近Go 101的作者釋出了11道Go面試題,非常有趣,打算寫一個系列對每道題做詳細解析,歡迎大家關注。
本題是Go quiz slice
系列的第2道題目,這道題非常有迷惑性。
通過這道題我們可以知曉對slice
做range遍歷的坑,避免在實際專案中踩坑。
package main
func main() {
var x = []string{"A", "B", "C"}
for i, s := range x {
print(i, s, ",")
x[i+1] = "M"
x = append(x, "Z")
x[i+1] = "Z"
}
}
- 0A,1B,2C,
- 0A,1Z,2Z,
- 0A,1M,2M,
- 0A,1M,2C,
- 0A,1Z,2M,
- 0A,1M,2Z,
- (infinite loop)
大家可以在評論區留下你們的答案。這道題主要有以下幾個考點:
slice
做range遍歷,Go編譯器背後會做哪些事情?slice
什麼時候擴容,擴容後的行為是怎麼樣的?
解析
我們先逐個解答上面的問題。
range遍歷機制
range對slice
做遍歷的時候,實際上是先構造一個原slice的拷貝,再對這個拷貝做遍歷。
在for迴圈裡面的邏輯執行之前,這個拷貝的值就確定下來了。因此這個拷貝的長度和容量是不會在for迴圈的時候發生改變的。
以上面的題目為例:range x
實際上是會先構造一個原切片x
的拷貝,我們假設為y
,然後對y
做遍歷。
for i, s := range x {
print(i, s, ",")
x[i+1] = "M"
x = append(x, "Z")
x[i+1] = "Z"
}
上面這段程式碼可以等價為:
y := x
for i := 0; i < len(y); i++ {
print(i, y[i], ",")
x[i+1] = "M"
x = append(x, "Z")
x[i+1] = "Z"
}
slice擴容機制
通過append
函式給slice
新增元素的時候,有2種情況:
- 如果切片的容量足夠,就會在切片指向的底層陣列裡追加元素。
- 如果切片的容量不足以承載新新增的元素,就會開闢一個新的底層陣列,把原切片裡的元素拷貝過來,再追加新的元素。切片結構裡的指標會指向新的底層陣列。
答案
我們回到本文最開始的題目,逐行解析每行程式碼的執行結果。
程式碼 | 程式執行結果 |
---|---|
var x = []string{"A", "B", "C"} | x是一個切片,長度是3,容量是3,x指向的底層陣列的值是[ "A" "B" "C"] |
for i, s := range x | 編譯器先構造一個切片x 的拷貝,假設為切片y ,然後對y 做遍歷。y 的值在for迴圈執行前就確定下來了,長度為3,容量為3,固定不變。 |
print(i, s, ",") | 第一次for迴圈,i 的值是0,s 的值是切片y 裡下標索引為0的元素,值為"A",列印0A |
x[i+1] = "M" | 執行x[1] = "M",因為切片x 和y 現在指向同一個底層陣列,切片y 裡下標索引為1的元素的值也被改成了"M",y 指向的底層陣列的值為["A", "M", "C"] |
x = append(x, "Z") | 給切片x 新增新元素"Z",因為當前切片x 的長度為3,容量為3,容量已滿,不足以承載新增加的元素,所以要對x 的底層陣列做擴容,x 指向新的底層陣列,新底層陣列的值是["A", "M", "C", "Z"],y 還是指向原來的底層陣列,y 指向的底層陣列的值是["A", "M", "C"] |
後續for迴圈 | 因為從第2次for迴圈開始,x 和y 指向了不同的底層陣列,所以對切片x 的修改不會影響到y ,因此後面列印的結果依次是1M,2C |
所以本題的答案是 0A, 1M, 2C。
總結
對於slice,時刻想著對slice做了修改後,slice裡的3個欄位:指標,長度,容量是怎麼變的。
zen of go
- 對切片
x
做range遍歷,實際上是對x
的拷貝(假設為y
)做range遍歷,y
的值(包括y
結構體裡指向底層陣列的指標的值,y
的長度和容量)都在執行for
迴圈前確定下來了。 - 切片的底層資料結構和擴容機制,如果有不清楚的,參考我寫的slice底層原理篇,包含了slice的所有注意事項。
開源地址
文章和示例程式碼開源地址在GitHub: https://github.com/jincheng9/...
公眾號:coding進階
個人網站:https://jincheng9.github.io/
思考題
留下2道思考題,歡迎大家在評論區留下你們的答案。也可以在我的wx公號傳送訊息slice2
獲取答案和原因。
題目1:
package main func main() { var x = []string{"A", "B", "C"} for i, s := range x { print(i, s, " ") x = append(x, "Z") x[i+1] = "Z" } }
題目2
package main func main() { var y = []string{"A", "B", "C", "D"} var x = y[:3] for i, s := range x { print(i, s, ",") x = append(x, "Z") x[i+1] = "Z" } }