不知道大家有沒有發現在乙個函式內部對切片引數進行了排序後也會改變函式外部原來的切片中元素的順序,但是在函式內向切片增加了元素後在函式外的原切片卻沒有新增元素,更奇怪的是新增並排序後,外部的切片有可能元素數量和元素順序都不會變,這是為什麼呢?我們通過三個小測驗來解釋造成這個現象的原因。
下面的**的輸出什麼?
func main()
reverse(s)
fmt.println(s)
}func reverse(s int)
}
run it on the go playground →
上面的**中雖然通過值傳遞了s
,為什麼在函式呼叫後在外部仍能看到s
的變化?
大家都知道切片是指向底層陣列的指標,切片本身不儲存任何資料。這意味著即使在這裡按值傳遞切片,函式中的切片仍指向相同的記憶體位址。所以在reverse()
內部使用的切片是乙個不同的指標物件,但仍將指向相同的記憶體位址,共享相同的陣列。所以在函式呼叫之後,該陣列中的數字重新排列,函式外部的切片與內部的切片共享著相同的底層陣列,所以外部的s
表現出來的就是它也被排序了。
func main()
reverse(s)
fmt.println(s)
}func reverse(s int)
}
run it on the go playground →
這一次,在函式外面輸出s
時可以看到它保持了排序後的順序,但是之前的元素1
去哪了?
我們先看一下 slice 的定義
type slice struct
最後我們在reverse()
函式內部的切片中新增一些額外的數字。函式執行完後在外部列印切片s
看看會輸出什麼。
func main()
reverse(s)
fmt.println(s)
}func reverse(s int)
}
run it on the go playground →
在我們的最終測驗中,不僅切片長度沒有保留,而且切片的順序也不受影響。為什麼?
我們可以通過使用cap
函式來檢查傳遞給reverse()
的切片的容量來驗證正在發生的事情。
func reverse(s int)
for i, j := 0, len(s)-1; i < j; i++
}
run it on the go playground →
只要不超出切片的容量,我們最終就會在main()函式中看到reverse函式對切片進行的更改。我們仍然看不到長度的變化,但是我們將看到切片的底層陣列中元素的重新排列。
如果我們恰巧在**中建立了從現有切片或陣列派生的新切片,那麼我們也可以看到相同的效果。例如,如果您呼叫s2:=s[:]
然後將s2
傳遞到我們的reverse()
函式中,則可能最終仍會影響s
,因為s2
和s
都指向同乙個支援陣列。同樣,如果我們向s2附加新元素,最終導致其超出支援陣列,我們將不再看到對乙個切片的更改會影響另乙個切片。
嚴格來說,這不是一件壞事。通過在絕對需要之前不隨意複製基礎陣列,我們最終獲得了效率更高的**,但編寫**時需要考慮到這一點,所以想確保在函式外也能看到函式內程式對切片的更改,那麼在函式中一定要把新切片 return 給外部,即使切片是一種引用型別。這也是不要其他程式語言經驗帶入到 go上的原因。
這不僅限於切片。切片是最容易陷入此陷阱的型別,但是任何帶有指標的型別都可能受到影響。如下所示。
type a struct
type b struct
func main() ,
ptr2: &b,
val: b,
} fmt.println(a.ptr1)
fmt.println(a.ptr2)
fmt.println(a.val)
demo(a)
fmt.println(a.ptr1)
fmt.println(a.ptr2)
fmt.println(a.val)
}func demo(a a)
a.val.str = "new-val-str"
}
run it on the go playground →
和這個例子類似,在 go 中切片的定義如下:
type slice struct
注意到array
字段實際上是乙個指標了嗎?這意味著切片會表現得像go中其他具有巢狀指標的任何型別一樣,實際上一點都不特殊,它只是恰好是很少有人關注其內部的型別。
最終,這意味著開發人員需要知道他們傳遞的資料型別以及所呼叫的函式可能會如何影響它們。當你將切片傳遞給其他函式或方法時,應該注意函式可能會,也可能不會更改原始切片中的元素。
同樣,你應始終意識到,內部帶有指標的結構很容易陷入相同的情況。除非指標本身被更新為引用記憶體中的另乙個物件,否則指標內部資料的任何更改都將被保留。
GO 切片實力踩坑
go 語言的切片這兩天用了用,可以支援切割陣列的中間部分.但今天使用中,出了 bug,查了半天,發現是切片的問題,簡單寫個 demo 復現當時的情況 package main import fmt func main b a 2 4 b 0 9 fmt.println a 你以為輸出的是什麼?來,看...
GO 切片實力踩坑
go 語言的切片這兩天用了用,可以支援切割陣列的中間部分.但今天使用中,出了 bug,查了半天,發現是切片的問題,簡單寫個 demo 復現當時的情況 package main import fmt func main b a 2 4 b 0 9 fmt.println a 你以為輸出的是什麼?來,看...
go語言web開發 排坑指南
本文章持續更新中.1 引數解析 func login w http.responsewriter,r http.request r.parseform 這個方法用來解析引數,不使用這個方法,r.form中不會包含相應的引數 2 build build 時要寫好路徑,例如 你要build webtes...