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)建切片。
- 通過(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。
- 通過(guò)字面量創(chuàng)建切片
slice := []int{1, 2, 3, 4}
上述代碼創(chuàng)建了長(zhǎng)度和容量均為4的整型切片。
- 通過(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(),有如下兩種方法:
- 函數(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
}
- 參數(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為不同的變量,所以自身所在的地址是不同的。