切片(slice)是建立在數組之上的更方便,更靈活,更強大的數據結構。切片并不存儲任何元素而只是對現有數組的引用。
切片的長度是指切片中元素的個數。切片的容量是指從切片的起始元素開始到其底層數組中的最后一個元素的個數。
問題:切片是建立在數組之上的,而數組本身不能改變長度,那么切片是如何動態改變長度的呢?實際發生的情況是,當新元素通過調用 append
函數追加到切片末尾時,如果超出了容量,append
內部會創建一個新的數組。并將原有數組的元素拷貝給這個新的數組,最后返回建立在這個新數組上的切片。這個新切片的容量是舊切片容量的二倍。下面的程序使事情變得容易理解:
package main
import (
"fmt"
)
func main() {
cars := []string{"Ferrari", "Honda", "Ford"}
fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6
}
在上面的程序中,cars
的容量開始時為 3。當我們追加了一個新的元素給 cars
,并將 append(cars, "Toyota")
的返回值重新賦值給 cars
。現在 cars
的容量翻倍,變為 6。
切片的零值為 nil
。一個 nil
切片的長度和容量都為 0。可以利用 append
函數給一個 nil
切片追加值。
package main
import (
"fmt"
)
func main() {
var names []string //zero value of a slice is nil
if names == nil {
fmt.Println("slice is nil going to append")
names = append(names, "John", "Sebastian", "Vinay")
fmt.Println("names contents:", names)
}
}
可以使用 ...
操作符將一個切片追加到另一個切片末尾:
package main
import (
"fmt"
)
func main() {
veggies := []string{"potatoes", "tomatoes", "brinjal"}
fruits := []string{"oranges", "apples"}
food := append(veggies, fruits...)
fmt.Println("food:", food)
}
上面的程序中,將 fruits 追加到 veggies 并賦值給 food。...
操作符用來展開切片。
可以認為切片在內部表示為如下的結構體:
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
可以看到切片包含長度、容量、以及一個指向首元素的指針。
切片保留對底層數組的引用。只要切片存在于內存中,數組就不能被垃圾回收。這在內存管理方面是值得關注的。假設我們有一個非常大的數組,而我們只需要處理它的一小部分,為此我們創建這個數組的一個切片,并處理這個切片。這里要注意的是,數組仍然存在于內存中,因為切片正在引用它。
解決該問題的一個方法是使用 copy 函數 func copy(dst, src []T) int
來創建該切片的一個拷貝。這樣我們就可以使用這個新的切片,原來的數組可以被垃圾回收。
package main
import (
"fmt"
)
func countries() []string {
countries := [...]string{"USA", "Singapore", "Germany", "India", "Australia"}
neededCountries := countries[:len(countries)-2]
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
return countriesCpy
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}
在上面程序中,第 9 行 neededCountries := countries[:len(countries)-2]
創建了一個底層數組為 countries
并排除最后兩個元素的切片。第 11 行將 neededCountries
拷貝到 countriesCpy
并在下一行返回 countriesCpy
。現在數組 countries
可以被垃圾回收,因為 neededCountries
不再引用。