Go語(yǔ)言Slice切片底層的實(shí)現(xiàn)
Go語(yǔ)言(Golang)中切片(slice)的相關(guān)知識(shí)、包括切片與數(shù)組的關(guān)系、底層結(jié)構(gòu)、擴(kuò)容機(jī)制、以及切片在函數(shù)傳遞、截取、增刪元素、拷貝等操作中的特性。并給出了相關(guān)代碼示例和一道面試題。關(guān)鍵要點(diǎn)包括:
數(shù)組特性:Go語(yǔ)言中數(shù)組是一個(gè)值、數(shù)組變量表示整個(gè)數(shù)組、不同于C語(yǔ)言中指向第一個(gè)元素的指針。傳遞數(shù)組到函數(shù)或拷貝數(shù)組時(shí)、會(huì)有不同的內(nèi)存地址和數(shù)據(jù)獨(dú)立性表現(xiàn)。
切片定義:切片是建立在Go數(shù)組之上的抽象類型、其底層結(jié)構(gòu)包含指向底層數(shù)組的指針、長(zhǎng)度和容量。
切片擴(kuò)容:
- 新切片長(zhǎng)度大于舊切片容量?jī)杀稌r(shí)、新容量為新長(zhǎng)度
- 舊容量小于256時(shí)、新容量為舊容量?jī)杀?/li>
- 否則按1.25倍增速擴(kuò)容、還會(huì)進(jìn)行內(nèi)存對(duì)齊。
函數(shù)傳遞:切片通過(guò)函數(shù)傳遞時(shí)、傳的是切片結(jié)構(gòu)、在函數(shù)內(nèi)改變切片可能影響函數(shù)外的切片、取決于底層數(shù)組是否變化。
切片操作:
- 通過(guò)
“:”
作截取切片、新切片與原切片共享底層數(shù)組 - 刪除元素可通過(guò)拼接切片實(shí)現(xiàn)
- 新增元素使用append操作
- 深度拷貝可使用copy函數(shù)。
1.切片是什么
在Go語(yǔ)言中 切片(slice)是建立在數(shù)組之上的一種抽象類型。切片提供了一種更靈活的方式來(lái)處理數(shù)組、它允許動(dòng)態(tài)地改變數(shù)組的大小、并且可以方便地進(jìn)行切片操作。理解切片之前、我們需要先了解數(shù)組。
Go的數(shù)組 在Go語(yǔ)言中、數(shù)組的長(zhǎng)度是類型的一部分、這意味著數(shù)組的長(zhǎng)度是固定的、不能改變。
數(shù)組的傳遞和拷貝行為與C語(yǔ)言不同、Go語(yǔ)言中的數(shù)組是值類型、傳遞數(shù)組時(shí)會(huì)進(jìn)行值拷貝。
1.1 示例一:
將數(shù)組傳遞到函數(shù)中 數(shù)組的地址不一樣
package main import "fmt" func main() { array := [3]int{1, 2, 3} // 數(shù)組傳遞到函數(shù)中 test(array) fmt.Printf("array 外: %p\n", &array) } func test(array [3]int) { fmt.Printf("array 內(nèi): %p\n", &array) }
由于數(shù)組是值類型、傳遞數(shù)組時(shí)會(huì)進(jìn)行值拷貝、因此在test
函數(shù)中打印的地址與main
函數(shù)中打印的地址不同。
1.2 值拷貝
值拷貝意味著拷貝的是變量的內(nèi)容、而不是內(nèi)存地址。因此拷貝出來(lái)的變量有自己的獨(dú)立副本、內(nèi)容相同、但它們存儲(chǔ)在不同的內(nèi)存地址中。
Go 語(yǔ)言中的切片(slice)是動(dòng)態(tài)擴(kuò)容的。當(dāng)你向切片中添加元素時(shí)、Go 會(huì)自動(dòng)管理切片的大小、并在需要時(shí)進(jìn)行擴(kuò)容。
具體行為:
- 初始容量:當(dāng)你創(chuàng)建一個(gè)切片時(shí)、Go 會(huì)為切片分配一個(gè)初始容量。如果你添加的元素超過(guò)了切片當(dāng)前的容量Go 會(huì)自動(dòng)擴(kuò)容。
- 擴(kuò)容規(guī)則:Go 會(huì)根據(jù)當(dāng)前切片的容量自動(dòng)擴(kuò)展切片的大小、通常是原來(lái)容量的2倍。擴(kuò)容后、切片的長(zhǎng)度和容量都會(huì)增加。
- 內(nèi)部機(jī)制:當(dāng)切片擴(kuò)容時(shí)、Go 會(huì)為新切片分配新的底層數(shù)組、并將原數(shù)組的元素拷貝到新數(shù)組中。這是一個(gè)代價(jià)比較高的操作、尤其是在需要多次擴(kuò)容的情況下
2.底層結(jié)構(gòu)
type slice struct { // 底層數(shù)組指針(或者說(shuō)是指向一塊連續(xù)內(nèi)存空間的起點(diǎn)) array unsafe.Pointer // 長(zhǎng)度 len int // 容量 cap int }
在這個(gè)結(jié)構(gòu)中:
array
:指向底層數(shù)組的指針、或者說(shuō)是指向一塊連續(xù)內(nèi)存空間的起點(diǎn)。len
:切片的長(zhǎng)度、即切片中實(shí)際包含的元素?cái)?shù)量。cap
:切片的容量、即切片可以包含的元素的最大數(shù)量,不包括可能的擴(kuò)展空間。
切片擴(kuò)容
計(jì)算目標(biāo)容量
case1:
如果新切片的長(zhǎng)度大于舊切片容量的兩倍、則新切片容量就為新切片的長(zhǎng)度。
case2:
- 如果舊切片的容量小于256、那么新切片的容量就是舊切片的容量的兩倍。
- 反之需要用舊切片容量按照1.25倍的增速、直到大于新切片長(zhǎng)度。
為了更平滑的過(guò)渡、每次擴(kuò)大1.25倍、還會(huì)加上3/4 * 256
進(jìn)行內(nèi)存對(duì)齊、需要按照Go內(nèi)存管理的級(jí)別去對(duì)齊內(nèi)存、最終容量以這個(gè)為準(zhǔn)。
3.切片問(wèn)題
3.1 切片通過(guò)函數(shù)傳的是什么
package main import ( "fmt" "reflect" "unsafe" ) func main() { s := make([]int, 5, 10) PrintSliceStruct(&s) test(s) } func test(s []int) { PrintSliceStruct(&s) } func PrintSliceStruct(s *[]int) { // 代碼 將slice 轉(zhuǎn)換成 reflect.SliceHeader ss := (*reflect.SliceHeader)(unsafe.Pointer(s)) // 查看slice的結(jié)構(gòu) fmt.Printf("slice struct: %+v, slice is %v\n", ss, s) }
控制臺(tái)輸出:
slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
切片的定義:你創(chuàng)建了一個(gè)切片 s、通過(guò) make([]int, 5, 10) 創(chuàng)建了一個(gè)長(zhǎng)度為 5、容量為 10 的切片。
也就是說(shuō)它初始化了一個(gè)包含 5 個(gè)元素且最大容量為 10 的底層數(shù)組
總結(jié):
切片傳遞:當(dāng)切片通過(guò)參數(shù)傳遞到函數(shù)時(shí)、傳遞的是切片的值、但切片內(nèi)部的底層數(shù)組地址(指針)并沒(méi)有被復(fù)制。
PrintSliceStruct
打印的結(jié)構(gòu):無(wú)論是在 main 函數(shù)還是 test 函數(shù)中、切片的底層數(shù)組地址、長(zhǎng)度和容量都是相同的、因?yàn)榈讓訑?shù)組是共享的。
為什么輸出相同:
輸出顯示的 Data 地址、Len 和 Cap 是一致的、因?yàn)?test(s) 傳遞的是切片的值(即切片的結(jié)構(gòu)),但切片中的指針指向相同的底層數(shù)組。所以無(wú)論是傳遞給 PrintSliceStruct
函數(shù)的 s、還是 test 函數(shù)中的 s、它們指向的是同一個(gè)底層數(shù)組、并且它們的長(zhǎng)度和容量保持一致
3.2 在函數(shù)里面改變切片 函數(shù)外的切片會(huì)被影響嗎
package main import ( "fmt" "reflect" "unsafe" ) func main() { s := make([]int, 5) // 創(chuàng)建一個(gè)長(zhǎng)度為 5 的切片 case1(s) // 調(diào)用 case1 函數(shù) case2(s) // 調(diào)用 case2 函數(shù) PrintSliceStruct(&s) // 打印切片結(jié)構(gòu) } // 底層數(shù)組不變 func case1(s []int) { s[1] = 1 // 修改切片中的元素 PrintSliceStruct(&s) // 打印切片結(jié)構(gòu) } // 底層數(shù)組變化 func case2(s []int) { s = append(s, 0) // 擴(kuò)容切片 s[1] = 1 // 修改切片中的元素 PrintSliceStruct(&s) // 打印切片結(jié)構(gòu) } func PrintSliceStruct(s *[]int) { // 將切片轉(zhuǎn)換成 reflect.SliceHeader ss := (*reflect.SliceHeader)(unsafe.Pointer(s)) // 打印切片的底層結(jié)構(gòu) fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s) }
關(guān)鍵點(diǎn):
case1
函數(shù):
在 case1
中、你傳入一個(gè)長(zhǎng)度為 5 的切片 s、并修改切片中的元素。
切片在函數(shù)內(nèi)的操作是對(duì)原切片的修改、因此底層數(shù)組沒(méi)有發(fā)生變化、切片的容量、長(zhǎng)度仍然相同。
打印的 slice struct
的 Data、Len
和 Cap
字段顯示的是切片的原始底層數(shù)據(jù)結(jié)構(gòu)。
case2
函數(shù):
在 case2
中、你向切片添加一個(gè)元素(通過(guò) append 操作)、這將可能導(dǎo)致切片的底層數(shù)組擴(kuò)容。
因?yàn)?nbsp;append
操作在超出當(dāng)前容量時(shí)會(huì)觸發(fā)擴(kuò)容、所以 s 的底層數(shù)組會(huì)發(fā)生變化、容量也可能增加。
在 case2
中、s 被賦值為 append(s, 0)、這將導(dǎo)致原有切片 s 的底層數(shù)組被擴(kuò)展、并且一個(gè)新的數(shù)組被分配給 s(s 指向的是新的底層數(shù)組)
打印時(shí)會(huì)看到 slice struct
中的 Data 指向一個(gè)新的地址、表示底層數(shù)組已經(jīng)發(fā)生了變化。
append(s, 0)
函數(shù)會(huì)檢查切片 s
是否有足夠的容量來(lái)存儲(chǔ)新的元素。如果切片的容量不足、append
函數(shù)會(huì)分配一個(gè)新的更大的數(shù)組、并復(fù)制舊數(shù)組的內(nèi)容到新數(shù)組中、然后將新元素添加到新數(shù)組的末尾、并更新切片的指針以指向包含新元素的新底層數(shù)組。
3.3 截取切片
package main import ( "fmt" "reflect" "unsafe" ) func main() { s := make([]int, 5) // 創(chuàng)建一個(gè)長(zhǎng)度為 5 的切片,默認(rèn)初始化為 [0 0 0 0 0] case1(s) // 調(diào)用 case1,修改切片內(nèi)容 case2(s) // 調(diào)用 case2,修改切片并改變底層數(shù)組 case3(s) // 調(diào)用 case3,截取切片并改變其長(zhǎng)度 case4(s) // 調(diào)用 case4,截取切片的部分元素 PrintSliceStruct(&s) // 最后打印切片的底層結(jié)構(gòu) } // case1:修改切片元素、底層數(shù)組不變 func case1(s []int) { s[1] = 1 // 修改切片中的第二個(gè)元素,s[1] = 1 PrintSliceStruct(&s) // 打印修改后的切片底層結(jié)構(gòu) } // case2:重新賦值為新的切片 func case2(s []int) { s = s[:] // 這里實(shí)際上并沒(méi)有改變切片的內(nèi)容、它只是重新賦值為原切片的一個(gè)新引用。 PrintSliceStruct(&s) // 打印新的切片底層結(jié)構(gòu) } // case3:截取切片、底層數(shù)組不變 func case3(s []int) { s = s[:len(s)-1] // 截取切片、去掉最后一個(gè)元素、新的切片長(zhǎng)度為 4 PrintSliceStruct(&s) // 打印截取后的切片底層結(jié)構(gòu) } // case4:截取切片的部分元素、底層數(shù)組不變 func case4(s []int) { sl := s[1:2] // 截取 s[1:2],即取出切片中索引為 1 的元素 PrintSliceStruct(&sl) // 打印截取后的新切片底層結(jié)構(gòu) } // PrintSliceStruct 打印切片的底層結(jié)構(gòu) func PrintSliceStruct(s *[]int) { // 將切片的指針轉(zhuǎn)換為 reflect.SliceHeader 結(jié)構(gòu)體,通過(guò) unsafe.Pointer 獲取底層數(shù)據(jù) ss := (*reflect.SliceHeader)(unsafe.Pointer(s)) // 打印切片的底層數(shù)據(jù)結(jié)構(gòu)、包括:指向底層數(shù)組的內(nèi)存地址、切片的長(zhǎng)度和容量 fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s) }
總結(jié):
切片操作的影響:
修改切片元素不會(huì)改變底層數(shù)組的地址。
重新賦值切片并沒(méi)有改變底層數(shù)組、除非涉及擴(kuò)容(例如 append)。
截取切片時(shí)、底層數(shù)組不變、切片的長(zhǎng)度和容量可能會(huì)變化。
3.4 刪除元素
package main import ( "fmt" "reflect" "unsafe" ) func main() { // 創(chuàng)建一個(gè)包含5個(gè)整數(shù)的切片 s := []int{0, 1, 2, 3, 4} // 打印切片的底層結(jié)構(gòu) PrintSliceStruct(&s) // 刪除切片中的最后一個(gè)元素,正確的做法是通過(guò)切片截取 _ = s[4] // 訪問(wèn)并丟棄切片中的最后一個(gè)元素 s1 := append(s[:1], s[2:]...) // 刪除元素 s[1], //s[:1](即切片 [0])和 s[2:](即切片 [2, 3, 4])拼接在一起。 // 打印修改后的切片 fmt.Println(s) // [0 2 3 4 4] fmt.Println(s1) // [0, 2, 3, 4] // 打印切片底層結(jié)構(gòu) PrintSliceStruct(&s) PrintSliceStruct(&s1) // 訪問(wèn)切片的元素 s = s[:4] // 截取切片、刪除最后一個(gè)元素 _ = s[3] // 訪問(wèn)切片中的最后一個(gè)元素(索引為3的元素) } // 打印切片的底層結(jié)構(gòu) func PrintSliceStruct(s *[]int) { // 將切片轉(zhuǎn)換為 reflect.SliceHeader ss := (*reflect.SliceHeader)(unsafe.Pointer(s)) // 打印切片的底層結(jié)構(gòu) fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s) }
在 Go 中、切片操作需要特別注意切片的索引和截取。訪問(wèn)切片中的元素時(shí)要小心類型不匹配(例如不能將一個(gè)切片元素賦值給切片)。
控制臺(tái)輸出:
slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 1 2 3 4]
[0 2 3 4 4]
[0 2 3 4]
slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 2 3 4 4]
slice struct: &{Data:1374390755328 Len:4 Cap:5}, slice is [0 2 3 4]
打印原切片 s 時(shí)、它仍然指向原底層數(shù)組(長(zhǎng)度為 5、容量為 5)、而且由于 s[4] 在內(nèi)存中并沒(méi)有被移除、原底層數(shù)組中的最后一個(gè)元素 4 被保留、因此 s 顯示為 [0 2 3 4 4]。
簡(jiǎn)而言之s 顯示為 [0 2 3 4, 4] 是因?yàn)樵记衅牡讓訑?shù)組并沒(méi)有被修改、而 append
操作生成了一個(gè)新的切片(s1)并分配了新的底層數(shù)組。所以s 中仍然包含原數(shù)組中的所有元素、最后一個(gè) 4 仍然存在。
為什么 s變成了 [0, 2, 3, 4, 4]
append 會(huì)根據(jù)切片的容量決定是否會(huì)使用原來(lái)的底層數(shù)組。如果原切片的容量足夠大、append 就會(huì)直接修改原切片。
在這段代碼中、由于原始切片 s 的容量足夠大(原始切片 s 的容量為 5)、append 仍然修改了原始切片 s 的內(nèi)容。切片的 s 和 s1 都指向相同的底層數(shù)組。
重點(diǎn):
原切片 s 的容量沒(méi)有改變:s 底層的數(shù)組仍然包含原來(lái) s 的所有元素。
append 沒(méi)有重新分配新的底層數(shù)組:由于原切片的容量足夠、所以 append 在修改原底層數(shù)組時(shí)、并沒(méi)有創(chuàng)建新的底層數(shù)組。因此原始切片 s 中的 4 仍然存在。
修改后 s 中的元素為 [0, 2, 3, 4, 4]:雖然你刪除了 s[1] 這個(gè)元素、但 append 使得 s 的底層數(shù)組沒(méi)有發(fā)生變化,因此原始的 4 元素仍然保留在切片中。
結(jié)論:
append
操作有時(shí)會(huì)創(chuàng)建新的底層數(shù)組(如果容量不足)、但如果原切片的容量足夠、append 直接修改原切片的底層數(shù)組。在這種情況下原切片 s 會(huì)保持原來(lái)的容量和數(shù)據(jù)、導(dǎo)致 s 顯示為 [0, 2, 3, 4, 4],即最后一個(gè) 4 保留下來(lái)了。
3.5 新增元素
package main import ( "fmt" "reflect" "unsafe" ) func main() { case1() case2() case3() } // case1 函數(shù)展示了使用 append 在切片末尾添加元素的行為 func case1() { // 創(chuàng)建一個(gè)長(zhǎng)度為 3,容量為 3 的切片 s1 := make([]int, 3, 3) // 向切片添加一個(gè)元素 1,append 返回一個(gè)新的切片 s1 = append(s1, 1) // 打印切片的底層結(jié)構(gòu) PrintSliceStruct(&s1) //1 } // case2 函數(shù)展示了在原切片上使用 append 并打印切片結(jié)構(gòu)的變化 func case2() { // 創(chuàng)建一個(gè)長(zhǎng)度為 3,容量為 4 的切片 s1 := make([]int, 3, 4) // 向切片添加一個(gè)元素 1,append 會(huì)擴(kuò)展切片的長(zhǎng)度 s2 := append(s1, 1) // 打印原切片 s1 和新切片 s2 的底層結(jié)構(gòu) PrintSliceStruct(&s1)//2 PrintSliceStruct(&s2)//3 } // case3 函數(shù)與 case2 類似,展示了切片長(zhǎng)度、容量變化的行為 func case3() { // 創(chuàng)建一個(gè)長(zhǎng)度為 3,容量為 3 的切片 s1 := make([]int, 3, 3) // 向切片添加一個(gè)元素 1,append 返回一個(gè)新的切片 s2 := append(s1, 1) // 打印原切片 s1 和新切片 s2 的底層結(jié)構(gòu) PrintSliceStruct(&s1)//4 PrintSliceStruct(&s2)//5 } // PrintSliceStruct 打印切片的底層結(jié)構(gòu) func PrintSliceStruct(s *[]int) { // 使用 reflect 和 unsafe 包將切片轉(zhuǎn)換成 reflect.SliceHeader 結(jié)構(gòu)體 ss := (*reflect.SliceHeader)(unsafe.Pointer(s)) // 打印切片的底層結(jié)構(gòu) fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s) }
控制臺(tái)輸出
slice struct: &{Data:1374390755328 Len:4 Cap:6}, slice is [0 0 0 1]
slice struct: &{Data:1374390779936 Len:3 Cap:4}, slice is [0 0 0]
slice struct: &{Data:1374390779936 Len:4 Cap:4}, slice is [0 0 0 1]
slice struct: &{Data:1374390673552 Len:3 Cap:3}, slice is [0 0 0]
slice struct: &{Data:1374390755376 Len:4 Cap:6}, slice is [0 0 0 1]
case1:
使用 make([]int, 3, 3) 創(chuàng)建了一個(gè)長(zhǎng)度為 3,容量為 3 的切片 s1,初始內(nèi)容為 [0, 0, 0]。
然后append(s1, 1)
會(huì)將元素 1 添加到切片的末尾、生成一個(gè)新的切片并返回。由于容量是 3、append
會(huì)自動(dòng)擴(kuò)容新的切片長(zhǎng)度是 4
最后調(diào)用 PrintSliceStruct
打印 s1 切片的底層結(jié)構(gòu)。
case2:
make([]int, 3, 4)
創(chuàng)建了一個(gè)長(zhǎng)度為 3、容量為 4 的切片 s1
使用 append(s1, 1)
向切片添加元素 1、生成一個(gè)新切片 s2。由于 s1 的容量已足夠、不會(huì)觸發(fā)擴(kuò)容。
通過(guò) PrintSliceStruct
打印切片 s1 和 s2 的底層結(jié)構(gòu)。
s3 和 s4 參考上面
3.6 操作原來(lái)切片會(huì)影響新的切片嗎
在 Go 中、切片是引用類型,這意味著當(dāng)你創(chuàng)建一個(gè)新切片時(shí),它實(shí)際上可能會(huì)指向同一個(gè)底層數(shù)組。因此,如果你修改了原切片(比如通過(guò) append 或其他操作),它可能會(huì)影響到新切片,特別是在底層數(shù)組沒(méi)有被重新分配的情況下。
切片和底層數(shù)組
切片(slice)是一個(gè)非常輕量級(jí)的抽象,它包含了三個(gè)部分:指向底層數(shù)組的指針、切片的長(zhǎng)度和切片的容量。
當(dāng)你對(duì)切片進(jìn)行操作時(shí)(例如使用 append、copy 或直接修改),這些操作通常會(huì)影響到底層數(shù)組。
如果多個(gè)切片引用同一個(gè)底層數(shù)組,改變其中一個(gè)切片的內(nèi)容可能會(huì)影響到其他切片,尤其是在沒(méi)有擴(kuò)容時(shí)。
append操作
當(dāng)使用 append 函數(shù)時(shí)、如果切片的容量足夠、append 會(huì)直接在原底層數(shù)組上操作、不會(huì)創(chuàng)建新的底層數(shù)組。在這種情況下、修改原切片的內(nèi)容會(huì)影響到新切片、因?yàn)樗鼈冎赶蛳嗤牡讓訑?shù)組。
如果容量不足、append 會(huì)創(chuàng)建一個(gè)新的底層數(shù)組、并將原切片的數(shù)據(jù)復(fù)制到新數(shù)組中、這時(shí)原切片和新切片就指向不同的底層數(shù)組了、它們互不影響。
例子:
沒(méi)有擴(kuò)容:
s1 := []int{1, 2, 3} s2 := s1 // s2 指向與 s1 相同的底層數(shù)組 s1[0] = 100 // 修改 s1 中的第一個(gè)元素 fmt.Println(s1) // 輸出 [100, 2, 3] fmt.Println(s2) // 輸出 [100, 2, 3]
這里s1 和 s2 指向相同的底層數(shù)組,因此修改 s1 會(huì)影響到 s2。
擴(kuò)容時(shí):
s1 := []int{1, 2, 3} s2 := append(s1, 4) // s2 創(chuàng)建了新的底層數(shù)組 s1[0] = 100 // 修改 s1 中的第一個(gè)元素 fmt.Println(s1) // 輸出 [100, 2, 3] fmt.Println(s2) // 輸出 [1、2、3、 4]
這里s2 創(chuàng)建了一個(gè)新的底層數(shù)組,因此修改 s1 不會(huì)影響 s2。
結(jié)論:
修改原切片會(huì)影響新切片:如果新切片是通過(guò)引用原切片的底層數(shù)組創(chuàng)建的(沒(méi)有觸發(fā)擴(kuò)容)、修改原切片的內(nèi)容會(huì)影響到新切片。
擴(kuò)容時(shí)不影響:如果 append 或其他操作導(dǎo)致了擴(kuò)容、原切片和新切片就會(huì)指向不同的底層數(shù)組、互不影響。
4.字節(jié)面試題
下面這道題的輸出是什么
package main import "fmt" func main() { // 定義一個(gè)匿名函數(shù) doAppend,用來(lái)執(zhí)行 append 操作并打印切片的長(zhǎng)度和容量 doAppend := func(s []int) { s = append(s, 1) // 向切片中添加元素 1 printLengthAndCapacity(s) // 打印切片的長(zhǎng)度和容量 } // 創(chuàng)建一個(gè)長(zhǎng)度為 8,容量為 8 的切片 s s := make([]int, 8, 8) // 傳遞 s 的前 4 個(gè)元素(即 s[:4])到 doAppend doAppend(s[:4]) // 只傳遞前4個(gè)元素的切片 // 打印原始切片 s 的長(zhǎng)度和容量 printLengthAndCapacity(s) // 傳遞整個(gè)切片 s 到 doAppend doAppend(s) // 打印原始切片 s 的長(zhǎng)度和容量 printLengthAndCapacity(s) } func printLengthAndCapacity(s []int) { fmt.Println() fmt.Printf("len=%d cap=%d \n", len(s), cap(s)) // 打印切片的長(zhǎng)度和容量 }
len(s) 是切片的長(zhǎng)度。
cap(s) 是切片的容量、表示切片底層數(shù)組的大小。
len=5 cap=8 len=8 cap=8 len=9 cap=16 len=8 cap=8
調(diào)用 doAppend(s[:4]):
s[:4] 是 s 切片的前 4 個(gè)元素、創(chuàng)建一個(gè)新的切片 [0, 0, 0, 0],長(zhǎng)度為 4、容量為 8(因?yàn)樗玫氖窃衅牡讓訑?shù)組)。
在 doAppend 中、執(zhí)行 append(s, 1)、這會(huì)向切片添加一個(gè)元素 1、導(dǎo)致切片的長(zhǎng)度變?yōu)?5、容量保持為 8(因?yàn)樗鼪](méi)有觸發(fā)擴(kuò)容)。
打印結(jié)果為:len=5 cap=8。
調(diào)用 doAppend(s):
這次傳遞整個(gè) s 切片、長(zhǎng)度為 8、容量為 8。
執(zhí)行 append(s 1)
、這會(huì)向切片 s 添加一個(gè)元素 1。因?yàn)?s 的容量是 8、不能再容納更多元素、因此會(huì)觸發(fā)擴(kuò)容、新的底層數(shù)組的容量將是原來(lái)的 2 倍、即 16、長(zhǎng)度變?yōu)?9。
打印結(jié)果為:len=9 cap=16。
注意:
append 創(chuàng)建了一個(gè)新的底層數(shù)組、并返回了一個(gè)新的切片。如果你不把返回的新切片賦值回 s、原始切片 s 不會(huì)改變、仍然指向舊的底層數(shù)組。
由于 append(s 1) 返回的是一個(gè)新的切片、但并沒(méi)有將它賦值回 s、所以原始切片 s 的長(zhǎng)度和容量沒(méi)有變化、仍然是 len=8 和 cap=8
如果改成將新切片賦值回s
package main import "fmt" func main() { // 定義一個(gè)匿名函數(shù) doAppend 用于向切片添加元素 doAppend := func(s []int) { s = append(s, 1) // 向切片中添加元素 1 printLengthAndCapacity(s) // 打印切片的長(zhǎng)度和容量 } // 定義一個(gè)匿名函數(shù) doAppend 用于向切片添加元素 doAppends := func(s []int) []int { s = append(s, 1) // 使用 append 向切片添加一個(gè)元素 1 printLengthAndCapacity(s) return s } // 創(chuàng)建一個(gè)長(zhǎng)度為 8,容量為 8 的切片 s s := make([]int, 8, 8) // 傳遞前 4 個(gè)元素的切片 doAppend(s[:4]) // 只傳遞前4個(gè)元素的切片 printLengthAndCapacity(s) // 傳遞整個(gè)切片 s s = doAppends(s) // 將返回的新切片賦值回 s printLengthAndCapacity(s) } func printLengthAndCapacity(s []int) { fmt.Println() fmt.Printf("len=%d cap=%d \n", len(s), cap(s)) }
len=5 cap=8
len=8 cap=8
len=9 cap=16
len=9 cap=16
到此這篇關(guān)于Go語(yǔ)言Slice切片底層的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go語(yǔ)言Slice切片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言題解LeetCode599兩個(gè)列表的最小索引總和
這篇文章主要為大家介紹了Go語(yǔ)言題解LeetCode599兩個(gè)列表的最小索引總和示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12詳解Go語(yǔ)言實(shí)現(xiàn)線性查找算法和二分查找算法
線性查找又稱順序查找,它是查找算法中最簡(jiǎn)單的一種。二分查找,也稱折半查找,相比于線性查找,它是一種效率較高的算法。本文將用Go語(yǔ)言實(shí)現(xiàn)這兩個(gè)查找算法,需要的可以了解一下2022-12-12Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié)
在日常的一些開(kāi)發(fā)場(chǎng)景中,我們需要去和遠(yuǎn)程服務(wù)器進(jìn)行一些通信,本文主要介紹了Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-04-04Go語(yǔ)言通過(guò)Luhn算法驗(yàn)證信用卡卡號(hào)是否有效的方法
這篇文章主要介紹了Go語(yǔ)言通過(guò)Luhn算法驗(yàn)證信用卡卡號(hào)是否有效的方法,實(shí)例分析了Luhn算法的原理與驗(yàn)證卡號(hào)的使用技巧,需要的朋友可以參考下2015-03-03