Go Slice詳解

slice的存儲(chǔ)結(jié)構(gòu)

slice代表變長(zhǎng)的序列,它的底層是數(shù)組。一個(gè)切片由3部分組成:指針、長(zhǎng)度和容量。指針指向底層數(shù)組,長(zhǎng)度代表slice當(dāng)前的長(zhǎng)度,容量代表底層數(shù)組的長(zhǎng)度。
換句話說(shuō),slice自身維護(hù)了一個(gè)指針屬性,指向它底層數(shù)組的某些元素的集合。

type slice struct {
    array unsafe.Pointer // 指向底層數(shù)組
    len int // 長(zhǎng)度
    cap int // 容量
}

創(chuàng)建切片

一般有如下方法創(chuàng)建切片。

  1. 通過(guò)make函數(shù)創(chuàng)建
slice := make([]T, 5)

上述代碼創(chuàng)建了一個(gè)整型切片,其長(zhǎng)度為5。若不傳入容量的大小,則容量和長(zhǎng)度相同。

slice := make([]T, 3, 5)

上述代碼創(chuàng)建了一個(gè)整型切片,其長(zhǎng)度為3,容量為5。

  1. 通過(guò)字面量創(chuàng)建切片
slice := []int{1, 2, 3, 4}

上述代碼創(chuàng)建了長(zhǎng)度和容量均為4的整型切片。

  1. 通過(guò)切片創(chuàng)建切片
slice[i:j:k]

i代表起始位置,切片的長(zhǎng)度為(j-i),切片的容量為(k-i)。如果沒(méi)有指定k,則表示切到底層數(shù)組的尾部。此外還有幾種簡(jiǎn)化形式:

slice[i:] // 從i切到尾部
slice[:j] // 從頭部切到j(luò),不包括j
slice[:] // 從頭切到尾

nil slice和空slice

聲明一個(gè)slice,但是不初始化,這個(gè)slice就是nil slice。nil slice表示它的指針為nil,也就是這個(gè)slice不會(huì)指向底層數(shù)組,因此它的長(zhǎng)度和容量都為0。

var slice []int

創(chuàng)建一個(gè)長(zhǎng)度為0的slice,就是空slice。空slice的長(zhǎng)度和容量也為0,但是它會(huì)指向一個(gè)底層數(shù)組,只不過(guò)底層數(shù)組是長(zhǎng)度為0的空數(shù)組。

slice := make([]int,0)

copy()函數(shù)

可以將一個(gè)slice拷貝到另一個(gè)slice中。copy()函數(shù)表示將src拷貝到dst。若src比dst長(zhǎng),則截?cái)?;若src比dst短,則只拷貝src的部分。返回值是拷貝成功的元素?cái)?shù)量。

func copy(dst, src []Type) int

示例:

s1 := []int{11, 22, 33}
s2 := make([]int, 5)
s3 := make([]int,2)

num := copy(s2, s1)
copy(s3,s1)

// [11 22 33] 0xc0000160a8
fmt.Printf("the value of s1 is %v, the value of ptr of s1 is %p\n", s1, s1) 
// [11,22,33,0,0] 0xc00001c120
fmt.Printf("the value of s2 is %v, the value of ptr of s2 is %p\n", s2, s2)
// [11,22] 0xc00001a2d0 
fmt.Printf("the value of s3 is %v, the value of ptr os s3 is %p\n", s3, s3) 

s1拷貝到s2時(shí),因s1的長(zhǎng)度小于s2的長(zhǎng)度,只拷貝s1的部分,則s2為[11,22,33,0,0];s1拷貝到s3時(shí),因s3的長(zhǎng)度小于s1的長(zhǎng)度,所以截?cái)鄐1,只拷貝前兩個(gè)元素,則s3為[11, 22]。
copy()操作只是拷貝內(nèi)容,各切片的底層數(shù)組仍然是獨(dú)立的。

append()函數(shù)

使用append()函數(shù)可以追加元素。
在append時(shí),如果切片的容量已經(jīng)不能容納將要追加的數(shù)據(jù),就會(huì)創(chuàng)建一個(gè)新的擴(kuò)容后的底層數(shù)組,將之前的數(shù)據(jù)拷貝過(guò)去后,再執(zhí)行擴(kuò)容操作;如果切片的容量足以容納,那么就會(huì)在原數(shù)組執(zhí)行擴(kuò)容操作。
目前擴(kuò)展底層數(shù)組的邏輯為:按照當(dāng)前底層數(shù)組長(zhǎng)度的2倍進(jìn)行擴(kuò)容;如果底層數(shù)組的長(zhǎng)度超過(guò)1000,將按照125%擴(kuò)容。
下述代碼分別測(cè)試了在不會(huì)擴(kuò)容和會(huì)擴(kuò)容的前提下,執(zhí)行append操作的結(jié)果。

func main() {
  var slice = []int{1, 2, 3, 4, 5} // len = 5; capacity = 5
  var newSlice = slice[1:3]        // len = 2; capacity = 4(已經(jīng)使用了兩個(gè)位置,還有兩個(gè)位置可以append)

  fmt.Printf("%p\n", slice)    // 0xc00001c120
  fmt.Printf("%p\n", newSlice) // 0xc00001c128; newSlice的地址指向的是slice[1]的地址,因此底層使用的是同一個(gè)數(shù)組

  fmt.Printf("%v\n", slice)    // [1 2 3 4 5]
  fmt.Printf("%v\n", newSlice) // [2 3]

  newSlice[1] = 6              // 更改后slice、newSlice都改變了
  fmt.Printf("%v\n", slice)    // [1 2 6 4 5]
  fmt.Printf("%v\n", newSlice) // [2 6]

  newSlice = append(newSlice, 7, 8) // append操作之后,array的len和capacity不變, newArray的len變?yōu)?,capacity仍然為4
  fmt.Printf("%v\n", slice)         //[1 2 6 7 8]; newSlice改變了底層數(shù)組的內(nèi)容,所以slice的內(nèi)容也變了
  fmt.Printf("%v\n", newSlice)      //[2 6 7 8]

  newSlice = append(newSlice, 9, 10) // newSlice的len已經(jīng)等于cap,再次append會(huì)創(chuàng)建一個(gè)新的底層數(shù)組(已擴(kuò)容),并將array指向的底層數(shù)組拷貝過(guò)去,并追加新值。
  fmt.Printf("%p\n", slice)          // 0xc00001c120; slice指向的底層數(shù)組未改變
  fmt.Printf("%p\n", newSlice)       // 0xc00009e000; newSlice指向的底層數(shù)組有改變
  fmt.Printf("%v\n", slice)          // [1 2 6 7 8]
  fmt.Printf("%v\n", newSlice)       // [2 6 7 8 9 10]
}

slice傳參

在Go語(yǔ)言中,函數(shù)的參數(shù)都是按值傳遞的,因此在調(diào)用函數(shù)時(shí),會(huì)將參數(shù)的副本傳遞給函數(shù)。在傳遞slice時(shí),雖然傳遞的是副本,但是副本同樣指向了源slice的底層數(shù)組,所以在函數(shù)內(nèi)部修改slice,有可能會(huì)影響到底層數(shù)組,進(jìn)而影響到其他slice。

func main() {
  slice := []int{1, 2}
  // [1 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  change(slice)
  // [3 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  return
}

func change(slice []int) {
  slice[0] = 3
  // [3 2] 0xc00000e090 0xc00001a2d0
  fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
}

上述代碼將slice傳入change()函數(shù)并將slice[0]的值修改為3。在main()函數(shù)中打印出調(diào)用前后的slice值,調(diào)用前為[1,2],調(diào)用后為[3,2]。且無(wú)論是在main()函數(shù)中還是change()函數(shù)中,slice指向的底層數(shù)組的地址都是同一個(gè)。
但是若在函數(shù)內(nèi)部調(diào)用append()函數(shù),可能會(huì)生成一個(gè)新的底層數(shù)組。

func main() {
  slice := []int{1, 2}
  // [1 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  change(slice)
  // [1 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  slice[0] = 4
  // [4 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  return
}

func change(slice []int) {
  slice = append(slice, 3)
  // [1 2 3] 0xc00000e090 0xc0000180e0
  fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
}

上述代碼在change()內(nèi)部為slice追加元素3,由于slice的長(zhǎng)度和容量均為2,append()操作會(huì)導(dǎo)致slice的副本指向一個(gè)新的底層數(shù)組,因此slice的副本和slice指向的底層數(shù)組不再為同一個(gè)。
在調(diào)用change()后,將slice下標(biāo)為0的值修改為4,輸出slice的值為[4,2]而非[1,2,3],再次說(shuō)明了change()函數(shù)內(nèi)部的slice和main()函數(shù)的slice已經(jīng)不再指向同一個(gè)底層數(shù)組。
如果希望獲取到函數(shù)調(diào)用后的slice(),有如下兩種方法:

  1. 函數(shù)調(diào)用返回slice
func main() {
  slice := []int{1, 2}
  // [1 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  slice = change(slice)
  // [1 2 3] 0xc00000e048 0xc0000180e0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  slice[0] = 4
  // [4 2 3] 0xc00000e048 0xc0000180e0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  return
}

func change(slice []int) []int {
  slice = append(slice, 3)
  // [1 2 3] 0xc00000e090 0xc0000180e0
  fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  return slice
}
  1. 參數(shù)傳入指針
func main() {
  slice := []int{1, 2}
  // [1 2] 0xc00000e048 0xc00001a2d0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  change(&slice)
  // [1 2 3] 0xc00000e048 0xc0000180e0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  slice[0] = 4
  // [4 2 3] 0xc00000e048 0xc0000180e0
  fmt.Printf("slice is %v, addr is %p, ptr is %p\n", slice, &slice, slice)
  return
}

func change(slice *[]int) {
  *slice = append(*slice, 3)
  // [1 2 3] 0xc00000e048 0xc0000180e0
  fmt.Printf("in function. slice is %v, addr is %p, ptr is %p\n", *slice, slice, *slice)
}

切片的地址和切片的指針指向的地址

func main() {
  slice := make([]int, 2, 5)
  slice1 := slice
  slice[1] = 3
  fmt.Printf("slice is %v, slice1 is %v\n", slice, slice1)                                 // [0 3] [0 3]
  fmt.Printf("addr of slice is %p, addr of slice1 is %p\n", &slice, &slice1)               // 0xc00000e048 0xc00000e060
  fmt.Printf("value of ptr of slice is %p, value of ptr of slice1 is %p\n", slice, slice1) // 0xc00001c120 0xc00001c120
}

可以看出,slice代表了其指針指向的底層數(shù)組的地址,&slice代表了slice自身的地址。
由于slice和slice1指向相同的底層數(shù)組,所以地址相同;但是slice和slice1為不同的變量,所以自身所在的地址是不同的。

參考

Go基礎(chǔ)系列:Go slice詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Slice常見(jiàn)操作及底層原理實(shí)現(xiàn) 一 什么是Slice slice(切片)是一種數(shù)組結(jié)構(gòu),相當(dāng)于是一個(gè)動(dòng)態(tài)的數(shù)組,...
    假程序員的世界閱讀 5,383評(píng)論 0 4
  • 在 Go 語(yǔ)言中,切片(slice)可能是使用最為頻繁的數(shù)據(jù)結(jié)構(gòu)之一,切片類型為處理同類型數(shù)據(jù)序列提供一個(gè)方便而高...
    Sun東輝閱讀 1,009評(píng)論 0 1
  • 切片是 Go 中的一種基本的數(shù)據(jù)結(jié)構(gòu),使用這種結(jié)構(gòu)可以用來(lái)管理數(shù)據(jù)集合。切片的設(shè)計(jì)想法是由動(dòng)態(tài)數(shù)組概念而來(lái),為了開(kāi)...
    一縷殤流化隱半邊冰霜閱讀 11,479評(píng)論 21 55
  • 原文地址:深入理解 Go Slice 是什么 在 Go 中,Slice(切片)是抽象在 Array(數(shù)組)之上的特...
    EDDYCJY閱讀 1,358評(píng)論 0 12
  • array 和 slice 看似相似,卻有著極大的不同,但他們之間還有著千次萬(wàn)縷的聯(lián)系 slice 是引用類型、是...
    戚銀閱讀 1,112評(píng)論 1 4

友情鏈接更多精彩內(nèi)容