十一、切片
Go 語言切片是對數組的抽象,因此切片是引用類型。但自身是結構體,值拷貝傳遞。
Go 數組的長度不可改變,在特定場景中這樣的集合就不太適用,Go 中提供了一種靈活,功能強悍的內置類型切片("動態數組"),與數組
相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。
切片本身不擁有任何數據,它是對現有數組的引用。
切片遍歷方式和數組一樣,可以用len()求長度。表示可用元素數量,讀寫操作不能超過該限制。
1 切片的定義
你可以聲明一個未指定大小的數組來定義切片,切片不需要說明長度。
var 變量名 []類型
// 比如 var str []string
// var arr []int。
// []括號內什么都不寫 就是切片類型
或使用 make() 函數來創建切片:
var slice []type = make([]type, len)
// 也可以簡寫,其中 capacity 為可選參數。這里 len 是數組的長度並且也是切片的初始長度
slice := make([]type, len)
slice := make([]type, length, capacity)
2 創建切片的各種方式
func main() {
//1.聲明切片
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println(s1, s2, s3)
// 4.初始化賦值
var s4 []int = make([]int, 0, 0)
fmt.Println(s4)
s5 := []int{1, 2, 3}
fmt.Println(s5)
// 5.從數組切片
arr := [5]int{1, 2, 3, 4, 5} // 定義數組
var s6 []int // 定義切片
// 索引區間前閉后開
s6 = arr[1:4]
fmt.Println(s6)
}
一個切片在未初始化之前默認為 nil,長度為 0。
3 切片初始化
s :=[] int {1,2,3 }
直接初始化切片,[] 表示是切片類型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。
var arr = [...]int{0,1,2,3,4,5,6,7,8,9,}
var slice0 []int = arr[start:end]
var slice1 []int = arr[:end]
var slice2 []int = arr[start:]
var slice3 []int = arr[:]
var slice4 = arr[:len(arr)-1]
舉例:
var arr = [...]int{0,1,2,3,4,5,6,7,8,9,}
var slice0 []int = arr[2:8] // 左閉右開,len=high-low
var slice1 []int = arr[0:6] //0可以省略: var slice []int = arr[:end]
var slice2 []int = arr[5:10] //如果切片到結尾,可以省略: var slice[]int = arr[start:]
var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一個元素
fmt.Printf("arr %v\n", arr)
fmt.Printf("slice0 %v\n", slice0)
fmt.Printf("slice1 %v\n", slice1)
fmt.Printf("slice2 %v\n", slice2)
fmt.Printf("slice3 %v\n", slice3)
fmt.Printf("slice4 %v\n", slice4)
// 輸出:
arr [0 1 2 3 4 5 6 7 8 9]
slice0 [2 3 4 5 6 7]
slice1 [0 1 2 3 4 5]
slice2 [5 6 7 8 9]
slice3 [0 1 2 3 4 5 6 7 8 9]
slice4 [0 1 2 3 4 5 6 7 8]
4 len() 和 cap() 函數
切片是可索引的,並且可以由 len() 方法獲取長度。
切片提供了計算容量的方法 cap() 可以測量切片最長可以達到多少。
以下為具體實例:
package main
import "fmt"
func main() {
var numbers = make([]int,3,5)
printSlice(numbers)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// 輸出
len=3 cap=5 slice=[0 0 0]
5 追加切片元素
正如我們已經知道數組的長度是固定的,它的長度不能增加。 切片是動態的,使用 append
可以將新元素追加到切片上。append 函數的定義是
func append(s[]T,x ... T)[]T
// x … T 在函數定義中表示該函數接受參數 x 的個數是可變的。這些類型的函數被稱為[可變函數]。
// append :向 slice 尾部添加數據,返回新的 slice 對象。
如果切片由數組支持,並且數組本身的長度是固定的,那么切片如何具有動態長度。以及內部發生了什么?
當新的元素被添加到切片時,會創建一個新的數組。現有數組的元素被復制到這個新數組中,並返回這個新數組的新切片引用。
新切片的容量是舊切片的兩倍。
舉例:
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: [Ferrari Honda Ford] has old length 3 and capacity 3
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6
6 切片的長度和容量
切片的長度是切片中的元素數。
切片的容量是從創建切片索引位置開始的底層數組中元素數。
7 copy() 函數和內存優化
切片持有對底層數組的引用。只要切片在內存中,數組就不能被垃圾回收。在內存管理方面,這是需要注意的。讓我們假設我們有一個非
常大的數組,我們只想處理它的一小部分。然后,我們由這個數組創建一個切片,並開始處理切片。這里需要重點注意的是,在切片引用
時數組仍然存在內存中。
一種解決方法是使用 [copy] 函數 func copy(dst,src[]T)int
來生成一個切片的副本。這樣我們可以使用新的切片,原始數組可以被
垃圾回收。
func countries() []string {
countries := []string{"USA", "Singapore", "Germany", "India", "Australia"}
neededCountries := countries[:len(countries)-2] // 創建一個去掉尾部 2 個元素的切片
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) //copies neededCountries to countriesCpy
return countriesCpy
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}
//現在 countries 數組可以被垃圾回收, 因為 neededCountries 不再被引用。
8 切片的函數傳遞
切片在內部可由一個結構體類型表示。這是它的表現形式,
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
切片包含長度、容量和指向數組第零個元素的指針。當切片傳遞給函數時,即使它通過值傳遞,指針變量也將引用相同的底層數組。因
此,當切片作為參數傳遞給函數時,函數內所做的更改也會在函數外可見。