學習程序中對Slice的體會
- 基礎很重要
- 面試題
- 可能出現的面試題
在這里我就簡單的分享一下我學習Slice的體會與困惑,作為一名即將上岸Golang開發的學子,需要99%的汗水,
基礎很重要
首先我們得清楚什么是切片Slice,它是一個動態陣列,是一個參考型別,切片的長度可以變化的,并且它就是一個結構體,分別含有三個欄位:指向底層陣列的首地址、切片長度len、切片容量cap,然后怎么初始化、怎么使用,怎么宣告,怎么遍歷以及怎么計算當前的cap這些就不必要說了,基礎知識,但是強調一下:對于s[low : high]這種格式的運算式, 如果s是陣列或者字串, 則0 <= low <= high <= len(s),如果s是切片, 則0 <= low <= high <= cap(s),還有一種我后面才知道的,剛開始沒學到,就是宣告切片時:s := arr[0:3:3],最后一個數可以宣告該切片的容量,但是3必須小于cap(arr),并且s的當前容量也是3 - 0 = 3,
面試題
看代碼
func main() {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := arr[2:5] // 此時切片的長度為3, 容量為6
s2 := s1[2:4] // 因為s1容量為6,所以4仍然屬于合法的index
fmt.Println(len(s1), cap(s1), len(s2), cap(s2)) // 輸出:3 6 2 4
fmt.Println(s1, s2) // 又因為未發生擴容行為,所以s1,s2公用底層陣列, 故最后輸出[3 4 5] [5 6]
}
具體的輸出結果以及想法寫在了上面的注釋里,這里比較容易困惑的是為什么s2會有一個6,那是因為s2和s1指向底層的同一個陣列,并且s1的容量是6,切s1切到4明顯可以切,所以切的還是同一個arr陣列,自然5后面就是6了,
再看一段代碼
s := []int{2}
fmt.Println(s[1:])
這里會輸出什么?空[ ] 還是報錯超出可取范圍?首先我們來分析一下,對于s切片很明顯是一個len為1,cap也為1的切片,也就是說,根據官方檔案來說:切片可以切這個范圍0 <= low <= high <= cap(s),也就是說切片的low可以為len長度,并且[1:]表示切下標為1到后面所有,后面所有也就是len長度,所以就是這樣切[len:len],對于這種切法我們很清楚我們得到的切片是不包含后面這個下標的元素的,所有這樣切出來就是[ ]空切片,也就是說s[1:]切出一個空切片,但是如果我們進行[len+1:]來切的話就很明顯報錯了,超出了可以切的范圍~
繼續
arr := []int{1, 2, 3, 4, 5, 6, 7, 8}
s1 := arr[2:5:7]
s1 = append(s1, 999)
fmt.Println(s1, arr)
這里又會輸出什么呢?
答案是:[3 4 5 999] [1 2 3 4 5 999 7 8]
首先對arr切片進行切片得到的s1是通過arr[2:5:7]進行的,那么這個7就是取原多少容量,目前取7,然后再減去從第2下標開始切,所以s1的容量就是5~,那么s1 = [3,4,5],然后再通過append動態添加新元素到s1后面,但是s1是和arr指向相同的一個底層陣列,所以呢,s1的5的后面一位是有數值的并且是6,當append之后就被覆寫掉了,動態添加到了5后面,也就是6 -> 999,同時它改變了底層陣列的資料,所以指向的相同底層陣列的arr也會出現相同的改變,
殺手锏
a := []int{1, 2, 3, 4, 5}
b := a[0:2]
c := a[0:3:3]
b = append(b, 1)
c = append(c, 1)
fmt.Println("b:", b, "cap", cap(b))
fmt.Println("c:", c, "cap", cap(c))
fmt.Println("a:", a, "cap", cap(a))
fmt.Println("c:", c[0:6])
/*輸出結果
b: [1 2 1] cap 5
c: [1 2 1 1] cap 6
a: [1 2 1 4 5] cap 5
c: [1 2 1 1 0 0]
*/
剛開始基礎不扎實的時候,感覺很懵很亂
首先b切了a,并且從index=0開始切,所以b=[1 2],容量是5
c切了a,并且也是從index=0開始切,但是宣告了容量是3,所以c的容量是3-0=3 ; c=[1 2 3],
然后開始分別給b和c append一個數,由于b的容量是5所以append時,len=2,完全有足夠的空間可以放,所以不會發生擴容,所以b的第三個數是1,b=[1 2 1],并且同時改動了和a指向相同的底層陣列的資料,所以a會輸出[1 2 1 4 5];這里重點在c,c的容量是3,并且已經滿了,所以append的時候放不下了,這時候就會splice開始擴容,按照小于1024個位元組就擴容一倍,所以c的容量就變成了6,是怎么計算的?3個元素int型別按照64位計算機就是8個位元組,8 * 3 * 2 = 48;然后48 / 8 = 6,所以分配了6個int大小容量的新的底層陣列,并且把資料copy到了新底層陣列然后就可以成功append進去了,最后再讓c切片指向新的底層陣列,這也就是為什么c輸出的結果是[1 2 1 1 0 0 ]因為后面開辟出來的陣列默認零值,并且本來是在append之前原來的底層陣列的3就已經被替換成了1,所以會有這樣的結果,
以上就是我學習遇到的面試題~
可能出現的面試題
接下來就是我自己琢磨切片的容量的擴容情況
上代碼
package main
import "fmt"
func main() {
s1 := make([]byte, 1) //任何低于8個位元組的擴容后都是8個位元組,不是簡單的擴一倍,前提是放的下,
fmt.Println(s1, "", cap(s1)) //1
s1 = append(s1, 2)
fmt.Println(s1, "", cap(s1)) //8
//在append多個并且超出8就是16
s4 := []rune("hello")
fmt.Println(s4, "", cap(s4)) //5
s4 = append(s4, 'h', 'r') //rune = int32 是4個位元組
fmt.Println(s4, "", cap(s4)) //12 按記憶體分配的規格,分配5*4*2=40 => 直接給48個位元組的記憶體 (特殊)按8 16 32 48 64分配,不只是單純的擴容一倍
s2 := []int{1, 2}
s2 = append(s2, 3) //int 8 個位元組 8 * 2 *2 = 32 32 / 8 = 4 所以這個時候cap=4
s2 = append(s2, 4) //由于容量是4,目前只有3個,所以還放得下; 所以這個時候仍然cap=4
s2 = append(s2, 5) //由于目前4個容量已滿, 需要擴容當前容量,按一倍 => 8 * 4 * 2 = 64 ;總共需要64位元組大小的記憶體; 64 / 8 = 8 個int大小的容量 所以下面輸出8
fmt.Println("=", cap(s2)) //輸出8
s3 := []int{1, 2, 3, 4}
fmt.Println(s3, "", cap(s3)) //輸出4
s3 = append(s3, 3, 3, 3, 3, 3) // 4*8*2=64 64/8 = 8不夠同時放入5個數,所以就按(4+1)*8*2=80個位元組 80 / 8 = 10 個int容量 (特殊)
fmt.Println("=", cap(s3)) //輸出10
s5 := []string{"hello"}
fmt.Println(cap(s5)) //輸出1
s5 = append(s5, "world") //string是16個位元組單位 16*2 = 32 32/16 = 2
fmt.Println(cap(s5)) //輸出2
s5 = append(s5, "!") //16 * 2 * 2 = 64 64 /16 = 4
fmt.Println(cap(s5)) //輸出4
s5 = append(s5, "渺", "!", "!", "!", "!", "!") //當同時append多個資料超出 容量*2 的時候 ,容量按 按一倍擴容后超出多少個加多少個16位元組的記憶體 (特殊)
fmt.Println(cap(s5)) //擴容一倍=8,不夠放還差1個,所以輸出9,append再加cap也再加
}
以上就是我自己測驗琢磨并在注釋寫出了它的擴容規則,注意我這里說的擴容規則是我自己在看不到append()原始碼的情況下多次測驗并且認為的,不同資料型別會有不同的擴容規則,比如int32和int64不同~對此有啥意見的可以討論討論,順便我也可以學習學習各位大牛的想法,新手上路,多多指教,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/275441.html
標籤:其他
