這篇文章主要講解了“golang中的[]*T、*[]T和*[]*T分別是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“golang中的[]*T、*[]T和*[]*T分別是什么”吧!
站在用戶的角度思考問題,與客戶深入溝通,找到蒙陰網站設計與蒙陰網站推廣的解決方案,憑借多年的經驗,讓設計與互聯網技術結合,創造個性化、用戶體驗好的作品,建站類型包括:成都網站設計、成都網站建設、企業官網、英文網站、手機端網站、網站推廣、域名與空間、網絡空間、企業郵箱。業務覆蓋蒙陰地區。
最近看到一段十分詭異的代碼,包含了“[]*T”“*[]T”和“*[]*T”,乍一看都是一樣的,但我們仔細觀察就發現他們的不同之處。今天我們就來介紹一下golong的“[]*T”“*[]T”和“*[]*T”,了解一下他們之間的不同,一起來看看
作為一個 Go 語言新手,看到一切”詭異“的代碼都會感到好奇;比如我最近看到的幾個方法;偽代碼如下:
func FindA() ([]*T,error) { } func FindB() ([]T,error) { } func SaveA(data *[]T) error { } func SaveB(data *[]*T) error { }
相信大部分剛入門 Go 的新手看到這樣的代碼也是一臉懵逼,其中最讓人疑惑的就是:
[]*T *[]T *[]*T
這樣對切片的聲明,先不看后面兩種寫法;單獨看 []*T 還是很好理解的:
該切片中存放的是所有 T 的內存地址,會比存放 T 本身來說要更省空間,同時 []*T 在方法內部是可以修改 T 的值,而[]T 是修改不了。
func TestSaveSlice(t *testing.T) { a := []T{{Name: "1"}, {Name: "2"}} for _, t2 := range a { fmt.Println(t2) } _ = SaveB(a) for _, t2 := range a { fmt.Println(t2) } } func SaveB(data []T) error { t := data[0] t.Name = "1233" return nil } type T struct { Name string }
比如以上例子打印的是
{1} {2} {1} {2}
只有將方法修改為
func SaveB(data []*T) error { t := data[0] t.Name = "1233" return nil }
才能修改 T 的值:
&{1} &{2} &{1233} &{2}
下面重點來看看 []*T 與 *[]T 的區別,這里寫了兩個 append 函數:
func TestAppendA(t *testing.T) { x:=[]int{1,2,3} appendA(x) fmt.Printf("main %v\n", x) } func appendA(x []int) { x[0]= 100 fmt.Printf("appendA %v\n", x) }
先看第一種,輸出是結果是:
appendA [1000 2 3] main [1000 2 3]
說明在函數傳遞過程中,函數內部的修改能夠影響到外部。
下面我們再看一個例子:
func appendB(x []int) { x = append(x, 4) fmt.Printf("appendA %v\n", x) }
最終結果卻是:
appendA [1 2 3 4] main [1 2 3]
沒有影響到外部。
而當我們再調整一下會發現又有所不同:
func TestAppendC(t *testing.T) { x:=[]int{1,2,3} appendC(&x) fmt.Printf("main %v\n", x) } func appendC(x *[]int) { *x = append(*x, 4) fmt.Printf("appendA %v\n", x) }
最終的結果:
appendA &[1 2 3 4] main [1 2 3 4]
可以發現如果傳遞切片的指針時,使用 append 函數追加數據時會影響到外部。
在分析上面三種情況之前,我們先來了解下 slice 的數據結構。
直接查看源碼會發現 slice 其實就是一個結構體,只是不能直接對外訪問。
源碼地址 runtime/slice.go
其中有三個重要的屬性:
屬性 | 含義 |
---|---|
array | 底層存放數據的數組,是一個指針。 |
len | 切片長度 |
cap | 切片容量 cap>=len |
提到切片就不得不想到數組,可以這么理解:
切片是對數組的抽象,而數組則是切片的底層實現。
其實通過切片這個名字也不難看出,它就是從數組中切了一部分;相對于數組的固定大小,切片可以根據實際使用情況進行擴容。
所以切片也可以通過對數組"切一刀"獲得:
x1:=[6]int{0,1,2,3,4,5} x2 := x[1:4] fmt.Println(len(x2), cap(x2))
其中 x1 的長度與容量都是6。
x2 的長度與容量則為3和5。
x2 的長度很容易理解。
容量等于5可以理解為,當前這個切片最多可以使用的長度。
因為切片 x2 是對數組 x1 的引用,所以底層數組排除掉左邊一個沒有被引用的位置則是該切片最大的容量,也就是5。
以剛才的代碼為例:
func TestAppendA(t *testing.T) { x:=[]int{1,2,3} appendA(x) fmt.Printf("main %v\n", x) } func appendA(x []int) { x[0]= 100 fmt.Printf("appendA %v\n", x) }
在函數傳遞過程中,main 中的 x 與 appendA 函數中的 x 切片所引用的是同個數組。
所以在函數中對 x[0]=100,main函數中也能獲取到。
本質上修改的就是同一塊內存數據。
在上述例子中,在 appendB 中調用 append 函數追加數據后會發現 main 函數中并沒有受到影響,這里我稍微調整了一下示例代碼:
func TestAppendB(t *testing.T) { //x:=[]int{1,2,3} x := make([]int, 3,5) x[0] = 1 x[1] = 2 x[2] = 3 appendB(x) fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) } func appendB(x []int) { x = append(x, 444) fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x)) }
主要是修改了切片初始化方式,使得容量大于了長度,具體原因后續會說明。
輸出結果如下:
appendB [1 2 3 444] len=4,cap=5 main [1 2 3] len=3,cap=5
main 函數中的數據看樣子確實沒有受到影響;但細心的朋友應該會注意到 appendB 函數中的 x 在 append() 之后長度 +1 變為了4。
而在 main 函數中長度又變回了3.
這個細節區別就是為什么 append() "看似" 沒有生效的原因;至于為什么要說“看似”,再次調整了代碼:
func TestAppendB(t *testing.T) { //x:=[]int{1,2,3} x := make([]int, 3,5) x[0] = 1 x[1] = 2 x[2] = 3 appendB(x) fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) y:=x[0:cap(x)] fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y)) }
在剛才的基礎之上,以 append 之后的 x 為基礎再做了一個切片;該切片的范圍為 x 所引用數組的全部數據。
再來看看執行結果如何:
appendB [1 2 3 444] len=4,cap=5 main [1 2 3] len=3,cap=5 y [1 2 3 444 0] len=5,cap=5
會神奇的發現 y 將所有數據都打印出來,在 appendB 函數中追加的數據其實已經寫入了數組中,但為什么 x 本身沒有獲取到呢?
看圖就很容易理解了:
在appendB中確實是對原始數組追加了數據,同時長度也增加了。
但由于是值傳遞,所以 slice 這個結構體即便是修改了長度為4,也只是對復制的那個對象修改了長度,main 中的長度依然為3.
由于底層數組是同一個,所以基于這個底層數組重新生成了一個完整長度的切片便能看到追加的數據了。
所以這里本質的原因是因為 slice 是一個結構體,傳遞的是值,不管方法里如何修改長度也不會影響到原有的數據(這里指的是長度和容量這兩個屬性)。
還有一個需要注意:
剛才特意提到這里的例子稍有改變,主要是將切片的容量設置超過了數組的長度;
如果不做這個特殊設置會怎么樣呢?
func TestAppendB(t *testing.T) { x:=[]int{1,2,3} //x := make([]int, 3,5) x[0] = 1 x[1] = 2 x[2] = 3 appendB(x) fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) y:=x[0:cap(x)] fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y)) } func appendB(x []int) { x = append(x, 444) fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x)) }
輸出結果:
appendB [1 2 3 444] len=4,cap=6 main [1 2 3] len=3,cap=3 y [1 2 3] len=3,cap=3
這時會發現 main 函數中的 y 切片數據也沒有發生變化,這是為什么呢?
這是因為初始化 x 切片時長度和容量都為3,當在 appendB 函數中追加數據時,會發現沒有位置了。
這時便會進行擴容:
將老數據復制一份到新的數組中。
追加數據。
將新的數據內存地址返回給 appendB 中的 x .
同樣的由于是值傳遞,所以 appendB 中的切片換了底層數組對 main 函數中的切片沒有任何影響,也就導致最終 main 函數的數據沒有任何變化了。
有沒有什么辦法即便是在擴容時也能對外部產生影響呢?
func TestAppendC(t *testing.T) { x:=[]int{1,2,3} appendC(&x) fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) } func appendC(x *[]int) { *x = append(*x, 4) fmt.Printf("appendC %v\n", x) }
輸出結果為:
appendC &[1 2 3 4] main [1 2 3 4] len=4,cap=6
這時外部的切片就能受到影響了,其實原因也很簡單;
剛才也說了,因為 slice 本身是一個結構體,所以當我們傳遞指針時,就和平時自定義的 struct 在函數內部通過指針修改數據原理相同。
最終在 appendC 中的 x 的指針指向了擴容后的結構體,因為傳遞的是 main 函數中 x 的指針,所以同樣的 main 函數中的 x 也指向了該結構體。
所以總結一下:
切片是對數組的抽象,同時切片本身也是一個結構體。
參數傳遞時函數內部與外部引用的是同一個數組,所以對切片的修改會影響到函數外部。
如果發生擴容,情況會發生變化,同時擴容會導致數據拷貝;所以要盡量預估切片大小,避免數據拷貝。
對切片或數組重新生成切片時,由于共享的是同一個底層數組,所以數據會互相影響,這點需要注意。
切片也可以傳遞指針,但場景很少,還會帶來不必要的誤解;建議值傳值就好,長度和容量占用不了多少內存。
相信使用過切片會發現非常類似于 Java 中的 ArrayList,同樣是基于數組實現,也會擴容發生數據拷貝;這樣看來語言只是上層使用的選擇,一些通用的底層實現大家都差不多。
這時我們再看標題中的 []*T *[]T *[]*T 就會發現這幾個并沒有什么聯系,只是看起來很像容易唬人。
感謝各位的閱讀,以上就是“golang中的[]*T、*[]T和*[]*T分別是什么”的內容了,經過本文的學習后,相信大家對golang中的[]*T、*[]T和*[]*T分別是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創新互聯,小編將為大家推送更多相關知識點的文章,歡迎關注!
網站題目:golang中的[]*T、*[]T和*[]*T分別是什么
URL鏈接:http://vcdvsql.cn/article8/peggip.html
成都網站建設公司_創新互聯,為您提供軟件開發、網站設計、定制網站、云服務器、企業網站制作、網站改版
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯