十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
這篇文章主要講解了“切片傳遞的隱藏危機有哪些”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“切片傳遞的隱藏危機有哪些”吧!
創(chuàng)新互聯(lián)公司服務(wù)項目包括興和網(wǎng)站建設(shè)、興和網(wǎng)站制作、興和網(wǎng)頁制作以及興和網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,興和網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到興和省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
在Go的源碼庫或者其他開源項目中,會發(fā)現(xiàn)有些函數(shù)在需要用到切片入?yún)r,它采用是指向切片類型的指針,而非切片類型。這里未免會產(chǎn)生疑問:切片底層不就是指針指向底層數(shù)組數(shù)據(jù)嗎,為何不直接傳遞切片,兩者有什么區(qū)別?
例如,在源碼log包中,Logger對象上綁定了formatHeader方法,它的入?yún)ο?code>buf,其類型是*[]byte,而非[]byte。
1func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {}
有以下例子
1func modifySlice(innerSlice []string) {
2 innerSlice[0] = "b"
3 innerSlice[1] = "b"
4 fmt.Println(innerSlice)
5}
6
7func main() {
8 outerSlice := []string{"a", "a"}
9 modifySlice(outerSlice)
10 fmt.Print(outerSlice)
11}
12
13// 輸出如下
14[b b]
15[b b]
我們將modifySlice函數(shù)的入?yún)㈩愋透臑橹赶蚯衅闹羔?/p>
1func modifySlice(innerSlice *[]string) {
2 (*innerSlice)[0] = "b"
3 (*innerSlice)[1] = "b"
4 fmt.Println(*innerSlice)
5}
6
7func main() {
8 outerSlice := []string{"a", "a"}
9 modifySlice(&outerSlice)
10 fmt.Print(outerSlice)
11}
12
13// 輸出如下
14[b b]
15[b b]
在上面的例子中,兩種函數(shù)傳參類型得到的結(jié)果都一樣,似乎沒發(fā)現(xiàn)有什么區(qū)別。通過指針傳遞它看起來毫無用處,而且無論如何切片都是通過引用傳遞的,在兩種情況下切片內(nèi)容都得到了修改。
這印證了我們一貫的認知:函數(shù)內(nèi)對切片的修改,將會影響到函數(shù)外的切片。但,真的是如此嗎?
考證與解釋
在《你真的懂string與[]byte的轉(zhuǎn)換了嗎》一文中,我們講過切片的底層結(jié)構(gòu)如下所示。
1type slice struct {
2 array unsafe.Pointer
3 len int
4 cap int
5}
array是底層數(shù)組的指針,len表示長度,cap表示容量。
我們對上文中的例子,做以下細微的改動。
1func modifySlice(innerSlice []string) {
2 innerSlice = append(innerSlice, "a")
3 innerSlice[0] = "b"
4 innerSlice[1] = "b"
5 fmt.Println(innerSlice)
6}
7
8func main() {
9 outerSlice := []string{"a", "a"}
10 modifySlice(outerSlice)
11 fmt.Print(outerSlice)
12}
13
14// 輸出如下
15[b b a]
16[a a]
神奇的事情發(fā)生了,函數(shù)內(nèi)對切片的修改竟然沒能對外部切片造成影響?
為了清晰地明白發(fā)生了什么,將打印添加更多細節(jié)。
1func modifySlice(innerSlice []string) {
2 fmt.Printf("%p %v %p\n", &innerSlice, innerSlice, &innerSlice[0])
3 innerSlice = append(innerSlice, "a")
4 innerSlice[0] = "b"
5 innerSlice[1] = "b"
6 fmt.Printf("%p %v %p\n", &innerSlice, innerSlice, &innerSlice[0])
7}
8
9func main() {
10 outerSlice := []string{"a", "a"}
11 fmt.Printf("%p %v %p\n", &outerSlice, outerSlice, &outerSlice[0])
12 modifySlice(outerSlice)
13 fmt.Printf("%p %v %p\n", &outerSlice, outerSlice, &outerSlice[0])
14}
15
16// 輸出如下
170xc00000c060 [a a] 0xc00000c080
180xc00000c0c0 [a a] 0xc00000c080
190xc00000c0c0 [b b a] 0xc000022080
200xc00000c060 [a a] 0xc00000c080
在Go函數(shù)中,函數(shù)的參數(shù)傳遞均是值傳遞。那么,將切片通過參數(shù)傳遞給函數(shù),其實質(zhì)是復(fù)制了slice結(jié)構(gòu)體對象,兩個slice結(jié)構(gòu)體的字段值均相等。正常情況下,由于函數(shù)內(nèi)slice結(jié)構(gòu)體的array和函數(shù)外slice結(jié)構(gòu)體的array指向的是同一底層數(shù)組,所以當(dāng)對底層數(shù)組中的數(shù)據(jù)做修改時,兩者均會受到影響。
但是存在這樣的問題:如果指向底層數(shù)組的指針被覆蓋或者修改(copy、重分配、append觸發(fā)擴容),此時函數(shù)內(nèi)部對數(shù)據(jù)的修改將不再影響到外部的切片,代表長度的len和容量cap也均不會被修改。
為了讓讀者更清晰的認識到這一點,將上述過程可視化如下。



.png)
可以看到,當(dāng)切片的長度和容量相等時,發(fā)生append,就會觸發(fā)切片的擴容。擴容時,會新建一個底層數(shù)組,將原有數(shù)組中的數(shù)據(jù)拷貝至新數(shù)組,追加的數(shù)據(jù)也會被置于新數(shù)組中。切片的array指針指向新底層數(shù)組。所以,函數(shù)內(nèi)切片與函數(shù)外切片的關(guān)聯(lián)已經(jīng)徹底斬斷,它的改變對函數(shù)外切片已經(jīng)沒有任何影響了。
注意,切片擴容并不總是等倍擴容。為了避免讀者產(chǎn)生誤解,這里對切片擴容原則簡單說明一下(源碼位于src/runtime/slice.go 中的 growslice 函數(shù)):
切片擴容時,當(dāng)需要的容量超過原切片容量的兩倍時,會直接使用需要的容量作為新容量。否則,當(dāng)原切片長度小于1024時,新切片的容量會直接翻倍。而當(dāng)原切片的容量大于等于1024時,會反復(fù)地增加25%,直到新容量超過所需要的容量。
到此,我們終于知道為什么有些函數(shù)在用到切片入?yún)r,它需要采用指向切片類型的指針,而非切片類型。
1func modifySlice(innerSlice *[]string) {
2 *innerSlice = append(*innerSlice, "a")
3 (*innerSlice)[0] = "b"
4 (*innerSlice)[1] = "b"
5 fmt.Println(*innerSlice)
6}
7
8func main() {
9 outerSlice := []string{"a", "a"}
10 modifySlice(&outerSlice)
11 fmt.Print(outerSlice)
12}
13
14// 輸出如下
15[b b a]
16[b b a] 請記住,如果你只想修改切片中元素的值,而不會更改切片的容量與指向,則可以按值傳遞切片,否則你應(yīng)該考慮按指針傳遞。
例題鞏固
為了判斷讀者是否已經(jīng)真正理解上述問題,我將上面的例子做了兩個變體,讀者朋友們可以自測。
測試一
1func modifySlice(innerSlice []string) {
2 innerSlice[0] = "b"
3 innerSlice = append(innerSlice, "a")
4 innerSlice[1] = "b"
5 fmt.Println(innerSlice)
6}
7
8func main() {
9 outerSlice := []string{"a", "a"}
10 modifySlice(outerSlice)
11 fmt.Println(outerSlice)
12} 測試二
1func modifySlice(innerSlice []string) {
2 innerSlice = append(innerSlice, "a")
3 innerSlice[0] = "b"
4 innerSlice[1] = "b"
5 fmt.Println(innerSlice)
6}
7
8func main() {
9 outerSlice:= make([]string, 0, 3)
10 outerSlice = append(outerSlice, "a", "a")
11 modifySlice(outerSlice)
12 fmt.Println(outerSlice)
13} 測試一答案
1[b b a]
2[b a] 測試二答案
1[b b a]
2[b b]感謝各位的閱讀,以上就是“切片傳遞的隱藏危機有哪些”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對切片傳遞的隱藏危機有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!