偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

[]*T *[]T *[]*T 傻傻分不清楚

開發(fā) 后端
相信使用過切片會發(fā)現(xiàn)非常類似于 Java 中的 ArrayList,同樣是基于數(shù)組實現(xiàn),也會擴(kuò)容發(fā)生數(shù)據(jù)拷貝;這樣看來語言只是上層使用的選擇,一些通用的底層實現(xiàn)大家都差不多。

[[413416]]

本文轉(zhuǎn)載自微信公眾號「crossoverJie」,作者crossoverJie。轉(zhuǎn)載本文請聯(lián)系crossoverJie公眾號。

前言

作為一個 Go 語言新手,看到一切”詭異“的代碼都會感到好奇;比如我最近看到的幾個方法;偽代碼如下:

  1. func FindA() ([]*T,error) { 
  2.  
  3. func FindB() ([]T,error) { 
  4.  
  5. func SaveA(data *[]T) error { 
  6.  
  7. func SaveB(data *[]*T) error { 

相信大部分剛?cè)腴T Go 的新手看到這樣的代碼也是一臉懵逼,其中最讓人疑惑的就是:

  1. []*T 
  2. *[]T 
  3. *[]*T 

這樣對切片的聲明,先不看后面兩種寫法;單獨看 []*T 還是很好理解的:該切片中存放的是所有 T 的內(nèi)存地址,會比存放 T 本身來說要更省空間,同時 []*T 在方法內(nèi)部是可以修改 T 的值,而[]T 是修改不了。

  1. func TestSaveSlice(t *testing.T) { 
  2.  a := []T{{Name"1"}, {Name"2"}} 
  3.  for _, t2 := range a { 
  4.   fmt.Println(t2) 
  5.  } 
  6.  _ = SaveB(a) 
  7.  for _, t2 := range a { 
  8.   fmt.Println(t2) 
  9.  } 
  10.  
  11. func SaveB(data []T) error { 
  12.  t := data[0] 
  13.  t.Name = "1233" 
  14.  return nil 
  15.  
  16. type T struct { 
  17.  Name string 

比如以上例子打印的是

  1. {1} 
  2. {2} 
  3. {1} 
  4. {2} 

只有將方法修改為

  1. func SaveB(data []*T) error { 
  2.  t := data[0] 
  3.  t.Name = "1233" 
  4.  return nil 

才能修改 T 的值:

  1. &{1} 
  2. &{2} 
  3. &{1233} 
  4. &{2} 

示例

下面重點來看看 []*T 與 *[]T 的區(qū)別,這里寫了兩個 append 函數(shù):

  1. func TestAppendA(t *testing.T) { 
  2.  x:=[]int{1,2,3} 
  3.  appendA(x) 
  4.  fmt.Printf("main %v\n", x) 
  5. func appendA(x []int) { 
  6.  x[0]= 100 
  7.  fmt.Printf("appendA %v\n", x) 

先看第一種,輸出是結(jié)果是:

  1. appendA [1000 2 3] 
  2. main [1000 2 3] 

說明在函數(shù)傳遞過程中,函數(shù)內(nèi)部的修改能夠影響到外部。

下面我們再看一個例子:

  1. func appendB(x []int) { 
  2.  x = append(x, 4) 
  3.  fmt.Printf("appendA %v\n", x) 

最終結(jié)果卻是:

  1. appendA [1 2 3 4] 
  2. main [1 2 3] 

沒有影響到外部。

而當(dāng)我們再調(diào)整一下會發(fā)現(xiàn)又有所不同:

  1. func TestAppendC(t *testing.T) { 
  2.  x:=[]int{1,2,3} 
  3.  appendC(&x) 
  4.  fmt.Printf("main %v\n", x) 
  5. func appendC(x *[]int) { 
  6.  *x = append(*x, 4) 
  7.  fmt.Printf("appendA %v\n", x) 

最終的結(jié)果:

  1. appendA &[1 2 3 4] 
  2. main [1 2 3 4] 

可以發(fā)現(xiàn)如果傳遞切片的指針時,使用 append 函數(shù)追加數(shù)據(jù)時會影響到外部。

slice 原理

在分析上面三種情況之前,我們先來了解下 slice 的數(shù)據(jù)結(jié)構(gòu)。

直接查看源碼會發(fā)現(xiàn) slice 其實就是一個結(jié)構(gòu)體,只是不能直接對外訪問。

源碼地址 runtime/slice.go

其中有三個重要的屬性:

屬性 含義
array 底層存放數(shù)據(jù)的數(shù)組,是一個指針。
len 切片長度
cap 切片容量 cap>=len

提到切片就不得不想到數(shù)組,可以這么理解:

切片是對數(shù)組的抽象,而數(shù)組則是切片的底層實現(xiàn)。

其實通過切片這個名字也不難看出,它就是從數(shù)組中切了一部分;相對于數(shù)組的固定大小,切片可以根據(jù)實際使用情況進(jìn)行擴(kuò)容。

所以切片也可以通過對數(shù)組"切一刀"獲得:

  1. x1:=[6]int{0,1,2,3,4,5} 
  2. x2 := x[1:4] 
  3. fmt.Println(len(x2), cap(x2)) 

其中 x1 的長度與容量都是6。

  • x2 的長度與容量則為3和5。
  • x2 的長度很容易理解。

容量等于5可以理解為,當(dāng)前這個切片最多可以使用的長度。

因為切片 x2 是對數(shù)組 x1 的引用,所以底層數(shù)組排除掉左邊一個沒有被引用的位置則是該切片最大的容量,也就是5。

同一個底層數(shù)組

以剛才的代碼為例:

  1. func TestAppendA(t *testing.T) { 
  2.  x:=[]int{1,2,3} 
  3.  appendA(x) 
  4.  fmt.Printf("main %v\n", x) 
  5. func appendA(x []int) { 
  6.  x[0]= 100 
  7.  fmt.Printf("appendA %v\n", x) 

在函數(shù)傳遞過程中,main 中的 x 與 appendA 函數(shù)中的 x 切片所引用的是同個數(shù)組。

所以在函數(shù)中對 x[0]=100,main函數(shù)中也能獲取到。

本質(zhì)上修改的就是同一塊內(nèi)存數(shù)據(jù)。

值傳遞帶來的誤會

在上述例子中,在 appendB 中調(diào)用 append 函數(shù)追加數(shù)據(jù)后會發(fā)現(xiàn) main 函數(shù)中并沒有受到影響,這里我稍微調(diào)整了一下示例代碼:

  1. func TestAppendB(t *testing.T) { 
  2.  //x:=[]int{1,2,3} 
  3.  x := make([]int, 3,5) 
  4.  x[0] = 1 
  5.  x[1] = 2 
  6.  x[2] = 3 
  7.  appendB(x) 
  8.  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) 
  9. func appendB(x []int) { 
  10.  x = append(x, 444) 
  11.  fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x)) 

主要是修改了切片初始化方式,使得容量大于了長度,具體原因后續(xù)會說明。

輸出結(jié)果如下:

  1. appendB [1 2 3 444] len=4,cap=5 
  2. main [1 2 3] len=3,cap=5 

main 函數(shù)中的數(shù)據(jù)看樣子確實沒有受到影響;但細(xì)心的朋友應(yīng)該會注意到 appendB 函數(shù)中的 x 在 append() 之后長度 +1 變?yōu)榱?。

而在 main 函數(shù)中長度又變回了3.

這個細(xì)節(jié)區(qū)別就是為什么 append() "看似" 沒有生效的原因;至于為什么要說“看似”,再次調(diào)整了代碼:

  1. func TestAppendB(t *testing.T) { 
  2.  //x:=[]int{1,2,3} 
  3.  x := make([]int, 3,5) 
  4.  x[0] = 1 
  5.  x[1] = 2 
  6.  x[2] = 3 
  7.  appendB(x) 
  8.  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) 
  9.  
  10.  y:=x[0:cap(x)] 
  11.  fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y)) 

在剛才的基礎(chǔ)之上,以 append 之后的 x 為基礎(chǔ)再做了一個切片;該切片的范圍為 x 所引用數(shù)組的全部數(shù)據(jù)。

再來看看執(zhí)行結(jié)果如何:

  1. appendB [1 2 3 444] len=4,cap=5 
  2. main [1 2 3] len=3,cap=5 
  3. y [1 2 3 444 0] len=5,cap=5 

會神奇的發(fā)現(xiàn) y 將所有數(shù)據(jù)都打印出來,在 appendB 函數(shù)中追加的數(shù)據(jù)其實已經(jīng)寫入了數(shù)組中,但為什么 x 本身沒有獲取到呢?

看圖就很容易理解了:

  • 在appendB中確實是對原始數(shù)組追加了數(shù)據(jù),同時長度也增加了。
  • 但由于是值傳遞,所以 slice 這個結(jié)構(gòu)體即便是修改了長度為4,也只是對復(fù)制的那個對象修改了長度,main 中的長度依然為3.
  • 由于底層數(shù)組是同一個,所以基于這個底層數(shù)組重新生成了一個完整長度的切片便能看到追加的數(shù)據(jù)了。

所以這里本質(zhì)的原因是因為 slice 是一個結(jié)構(gòu)體,傳遞的是值,不管方法里如何修改長度也不會影響到原有的數(shù)據(jù)(這里指的是長度和容量這兩個屬性)。

切片擴(kuò)容

還有一個需要注意:

剛才特意提到這里的例子稍有改變,主要是將切片的容量設(shè)置超過了數(shù)組的長度;

如果不做這個特殊設(shè)置會怎么樣呢?

  1. func TestAppendB(t *testing.T) { 
  2.  x:=[]int{1,2,3} 
  3.  //x := make([]int, 3,5) 
  4.  x[0] = 1 
  5.  x[1] = 2 
  6.  x[2] = 3 
  7.  appendB(x) 
  8.  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) 
  9.  
  10.  y:=x[0:cap(x)] 
  11.  fmt.Printf("y %v len=%v,cap=%v\n", y,len(y),cap(y)) 
  12. func appendB(x []int) { 
  13.  x = append(x, 444) 
  14.  fmt.Printf("appendB %v len=%v,cap=%v\n", x,len(x),cap(x)) 

輸出結(jié)果:

  1. appendB [1 2 3 444] len=4,cap=6 
  2. main [1 2 3] len=3,cap=3 
  3. y [1 2 3] len=3,cap=3 

這時會發(fā)現(xiàn) main 函數(shù)中的 y 切片數(shù)據(jù)也沒有發(fā)生變化,這是為什么呢?

這是因為初始化 x 切片時長度和容量都為3,當(dāng)在 appendB 函數(shù)中追加數(shù)據(jù)時,會發(fā)現(xiàn)沒有位置了。

  • 這時便會進(jìn)行擴(kuò)容:
  • 將老數(shù)據(jù)復(fù)制一份到新的數(shù)組中。
  • 追加數(shù)據(jù)。

將新的數(shù)據(jù)內(nèi)存地址返回給 appendB 中的 x .

同樣的由于是值傳遞,所以 appendB 中的切片換了底層數(shù)組對 main 函數(shù)中的切片沒有任何影響,也就導(dǎo)致最終 main 函數(shù)的數(shù)據(jù)沒有任何變化了。

傳遞切片指針

有沒有什么辦法即便是在擴(kuò)容時也能對外部產(chǎn)生影響呢?

  1. func TestAppendC(t *testing.T) { 
  2.  x:=[]int{1,2,3} 
  3.  appendC(&x) 
  4.  fmt.Printf("main %v len=%v,cap=%v\n", x,len(x),cap(x)) 
  5. func appendC(x *[]int) { 
  6.  *x = append(*x, 4) 
  7.  fmt.Printf("appendC %v\n", x) 

輸出結(jié)果為:

  1. appendC &[1 2 3 4] 
  2. main [1 2 3 4] len=4,cap=6 

這時外部的切片就能受到影響了,其實原因也很簡單;

剛才也說了,因為 slice 本身是一個結(jié)構(gòu)體,所以當(dāng)我們傳遞指針時,就和平時自定義的 struct 在函數(shù)內(nèi)部通過指針修改數(shù)據(jù)原理相同。

最終在 appendC 中的 x 的指針指向了擴(kuò)容后的結(jié)構(gòu)體,因為傳遞的是 main 函數(shù)中 x 的指針,所以同樣的 main 函數(shù)中的 x 也指向了該結(jié)構(gòu)體。

總結(jié)

所以總結(jié)一下:

  • 切片是對數(shù)組的抽象,同時切片本身也是一個結(jié)構(gòu)體。
  • 參數(shù)傳遞時函數(shù)內(nèi)部與外部引用的是同一個數(shù)組,所以對切片的修改會影響到函數(shù)外部。
  • 如果發(fā)生擴(kuò)容,情況會發(fā)生變化,同時擴(kuò)容會導(dǎo)致數(shù)據(jù)拷貝;所以要盡量預(yù)估切片大小,避免數(shù)據(jù)拷貝。
  • 對切片或數(shù)組重新生成切片時,由于共享的是同一個底層數(shù)組,所以數(shù)據(jù)會互相影響,這點需要注意。
  • 切片也可以傳遞指針,但場景很少,還會帶來不必要的誤解;建議值傳值就好,長度和容量占用不了多少內(nèi)存。

相信使用過切片會發(fā)現(xiàn)非常類似于 Java 中的 ArrayList,同樣是基于數(shù)組實現(xiàn),也會擴(kuò)容發(fā)生數(shù)據(jù)拷貝;這樣看來語言只是上層使用的選擇,一些通用的底層實現(xiàn)大家都差不多。

 

這時我們再看標(biāo)題中的 []*T *[]T *[]*T 就會發(fā)現(xiàn)這幾個并沒有什么聯(lián)系,只是看起來很像容易唬人。

 

責(zé)任編輯:武曉燕 來源: crossoverJie
相關(guān)推薦

2021-03-10 08:56:37

Zookeeper

2024-02-29 09:08:56

Encoding算法加密

2022-05-15 21:52:04

typeTypeScriptinterface

2018-12-17 12:30:05

Kubernetes存儲存儲卷

2020-10-30 08:20:04

SD卡TF卡存儲

2023-09-03 21:18:07

Python編程語言

2020-03-03 17:35:09

Full GCMinor

2023-02-27 15:46:19

數(shù)據(jù)元元數(shù)據(jù)

2018-05-22 16:24:20

HashMapJavaJDK

2016-11-04 12:51:46

Unix網(wǎng)絡(luò)IO 模型

2024-11-04 00:00:03

viewportDOMSPA

2022-02-25 09:14:33

類變量共享實例變量

2021-11-09 06:01:35

前端JITAOT

2025-05-12 08:40:00

前端監(jiān)控DOM

2021-02-08 23:47:51

文件存儲塊存儲對象存儲

2020-11-11 07:32:18

MySQL InnoDB 存儲

2023-04-11 15:57:49

JavaScriptCSSHTML

2021-01-13 08:10:26

接口IEnumeratorIEnumerable

2019-11-21 14:22:12

WiFiWLAN區(qū)別

2021-02-14 22:33:23

Java字符字段
點贊
收藏

51CTO技術(shù)棧公眾號