中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

切片傳遞的隱藏危機有哪些

發布時間:2021-10-25 14:57:12 來源:億速云 閱讀:176 作者:iii 欄目:編程語言

這篇文章主要講解了“切片傳遞的隱藏危機有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“切片傳遞的隱藏危機有哪些”吧!

在Go的源碼庫或者其他開源項目中,會發現有些函數在需要用到切片入參時,它采用是指向切片類型的指針,而非切片類型。這里未免會產生疑問:切片底層不就是指針指向底層數組數據嗎,為何不直接傳遞切片,兩者有什么區別

例如,在源碼log包中,Logger對象上綁定了formatHeader方法,它的入參對象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函數的入參類型改為指向切片的指針

 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]
 

在上面的例子中,兩種函數傳參類型得到的結果都一樣,似乎沒發現有什么區別。通過指針傳遞它看起來毫無用處,而且無論如何切片都是通過引用傳遞的,在兩種情況下切片內容都得到了修改。

這印證了我們一貫的認知:函數內對切片的修改,將會影響到函數外的切片。但,真的是如此嗎?


切片傳遞的隱藏危機有哪些        

考證與解釋


     

在《你真的懂string與[]byte的轉換了嗎》一文中,我們講過切片的底層結構如下所示。

1type slice struct {
2    array unsafe.Pointer
3    len   int
4    cap   int
5}
 

array是底層數組的指針,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]
 

神奇的事情發生了,函數內對切片的修改竟然沒能對外部切片造成影響?

為了清晰地明白發生了什么,將打印添加更多細節。

 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函數中,函數的參數傳遞均是值傳遞。那么,將切片通過參數傳遞給函數,其實質是復制了slice結構體對象,兩個slice結構體的字段值均相等。正常情況下,由于函數內slice結構體的array和函數外slice結構體的array指向的是同一底層數組,所以當對底層數組中的數據做修改時,兩者均會受到影響。

但是存在這樣的問題:如果指向底層數組的指針被覆蓋或者修改(copy、重分配、append觸發擴容),此時函數內部對數據的修改將不再影響到外部的切片,代表長度的len和容量cap也均不會被修改。

為了讓讀者更清晰的認識到這一點,將上述過程可視化如下。

切片傳遞的隱藏危機有哪些

切片傳遞的隱藏危機有哪些

切片傳遞的隱藏危機有哪些

切片傳遞的隱藏危機有哪些

可以看到,當切片的長度和容量相等時,發生append,就會觸發切片的擴容。擴容時,會新建一個底層數組,將原有數組中的數據拷貝至新數組,追加的數據也會被置于新數組中。切片的array指針指向新底層數組。所以,函數內切片與函數外切片的關聯已經徹底斬斷,它的改變對函數外切片已經沒有任何影響了。

注意,切片擴容并不總是等倍擴容。為了避免讀者產生誤解,這里對切片擴容原則簡單說明一下(源碼位于src/runtime/slice.go 中的 growslice 函數):

切片擴容時,當需要的容量超過原切片容量的兩倍時,會直接使用需要的容量作為新容量。否則,當原切片長度小于1024時,新切片的容量會直接翻倍。而當原切片的容量大于等于1024時,會反復地增加25%,直到新容量超過所需要的容量。

到此,我們終于知道為什么有些函數在用到切片入參時,它需要采用指向切片類型的指針,而非切片類型。

 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]
 

請記住,如果你只想修改切片中元素的值,而不會更改切片的容量與指向,則可以按值傳遞切片,否則你應該考慮按指針傳遞。


切片傳遞的隱藏危機有哪些        

例題鞏固

     

為了判斷讀者是否已經真正理解上述問題,我將上面的例子做了兩個變體,讀者朋友們可以自測。

測試一

 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]

感謝各位的閱讀,以上就是“切片傳遞的隱藏危機有哪些”的內容了,經過本文的學習后,相信大家對切片傳遞的隱藏危機有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

go
AI

郁南县| 甘南县| 尼勒克县| 牟定县| 大新县| 雅江县| 成都市| 汝州市| 太仆寺旗| 靖远县| 天等县| 东乌珠穆沁旗| 平阴县| 临潭县| 阿拉善右旗| 临夏县| 化德县| 建始县| 井研县| 工布江达县| 新郑市| 延津县| 钟山县| 荣昌县| 浦县| 南康市| 桐城市| 曲沃县| 阿巴嘎旗| 禄劝| 泸水县| 建始县| 绥滨县| 区。| 崇阳县| 四子王旗| 天长市| 凤庆县| 松原市| 万山特区| 杭州市|