切片(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
不再引用。