go語言筆記——切片底層本質是共享數組內存!!!絕對不要用指針指向 slice切片本身已經是一個引用類型就是指針


切片

切片(slice)是對數組一個連續片段的引用(該數組我們稱之為相關數組,通常是匿名的),所以切片是一個引用類型(因此更類似於 C/C++ 中的數組類型,或者 Python 中的 list 類型)

切片是一個 長度可變的數組。

多個切片如果表示同一個數組的片段,它們可以共享數據;因此一個切片和相關數組的其他切片是共享存儲的,相反,不同的數組總是代表不同的存儲。數組實際上是切片的構建塊。

優點 因為切片是引用,所以它們不需要使用額外的內存並且比使用數組更有效率,所以在 Go 代碼中 切片比數組更常用。

聲明切片的格式是: var identifier []type(不需要說明長度)。

一個切片在未初始化之前默認為 nil,長度為 0。

切片也可以用類似數組的方式初始化:var x = []int{2, 3, 5, 7, 11}。這樣就創建了一個長度為 5 的數組並且創建了一個相關切片。

切片在內存中的組織方式實際上是一個有 3 個域的結構體:指向相關數組的指針,切片 長度以及切片容量。下圖給出了一個長度為 2,容量為 4 的切片。——注意看其中的內存共享方式!!!

  • y[0] = 3 且 y[1] = 5
  • 切片 y[0:4] 由 元素 3, 5, 7 和 11 組成。

 

注意 絕對不要用指針指向 slice。切片本身已經是一個引用類型,所以它本身就是一個指針!!

7.2.2 將切片傳遞給函數

如果你有一個函數需要對數組做操作,你可能總是需要把參數聲明為切片。當你調用該函數時,把數組分片,創建為一個 切片引用並傳遞給該函數。這里有一個計算數組元素和的方法:

func sum(a []int) int { s := 0 for i := 0; i < len(a); i++ { s += a[i] } return s } func main() { var arr = [5]int{0, 1, 2, 3, 4} sum(arr[:]) }

7.2.3 用 make() 創建一個切片

當相關數組還沒有定義時,我們可以使用 make() 函數來創建一個切片 同時創建好相關數組:var slice1 []type = make([]type, len)

下圖描述了使用 make 方法生成的切片的內存結構:

7.2.4 new() 和 make() 的區別

看起來二者沒有什么區別,都在堆上分配內存,但是它們的行為不同,適用於不同的類型。

  • new(T) 為每個新的類型T分配一片內存,初始化為 0 並且返回類型為*T的內存地址:這種方法 返回一個指向類型為 T,值為 0 的地址的指針,它適用於值類型如數組和結構體(參見第 10 章);它相當於 &T{}
  • make(T) 返回一個類型為 T 的初始值,它只適用於3種內建的引用類型:切片、map 和 channel(參見第 8 章,第 13 章)。

換言之,new 函數分配內存,make 函數初始化;下圖給出了區別:

在圖 7.3 的第一幅圖中:

var p *[]int = new([]int) // *p == nil; with len and cap 0 p := new([]int)

在第二幅圖中, p := make([]int, 0) ,切片 已經被初始化,但是指向一個空的數組。

以上兩種方式實用性都不高。下面的方法:

var v []int = make([]int, 10, 50)

或者

v := make([]int, 10, 50)

這樣分配一個有 50 個 int 值的數組,並且創建了一個長度為 10,容量為 50 的 切片 v,該 切片 指向數組的前 10 個元素。

轉自:https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md

 

7.6.3 字符串和切片的內存結構

字符串 string s = "hello" 和子字符串 t = s[2:3] 在內存中的結構可以用下圖表示:

 

7.6.8 切片和垃圾回收

切片的底層指向一個數組,該數組的實際容量可能要大於切片所定義的容量。只有在沒有任何切片指向的時候,底層的數組內層才會被釋放,這種特性有時會導致程序占用多余的內存。

示例 函數 FindDigits 將一個文件加載到內存,然后搜索其中所有的數字並返回一個切片。

var digitRegexp = regexp.MustCompile("[0-9]+") func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) return digitRegexp.Find(b) }

這段代碼可以順利運行,但返回的 []byte 指向的底層是整個文件的數據。只要該返回的切片不被釋放,垃圾回收器就不能釋放整個文件所占用的內存。換句話說,一點點有用的數據卻占用了整個文件的內存。

想要避免這個問題,可以通過拷貝我們需要的部分到一個新的切片中:

func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = digitRegexp.Find(b) c := make([]byte, len(b)) copy(c, b) return c }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM