切片(slice)本身并非動(dòng)態(tài)數(shù)組或數(shù)組指針踩官。它內(nèi)部通過指針引用底層數(shù)組瞬逊,設(shè)定相關(guān)屬性將數(shù)據(jù)讀寫操作限定在指定區(qū)域轨香。
切片(slice)是建立在數(shù)組之上的更方便专普,更靈活,更強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)弹沽。切片并不存儲(chǔ)任何元素而只是對(duì)現(xiàn)有數(shù)組的引用。
創(chuàng)建切片
元素類型為 T 的切片表示為: [ ]T筋粗。
func main() {
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:4] //creates a slice from a[1] to a[3]
fmt.Println(b)
}
通過 a[start:end]
這樣的語法創(chuàng)建了一個(gè)從 a[start]
到 a[end -1]
的切片策橘。在上面的案例中, a[1:4]
創(chuàng)建了一個(gè)從 a[1]
到 a[3]
的切片娜亿。因此 b
的值為:[77 78 79]
丽已。
另外一個(gè)創(chuàng)建方式
func main() {
c := []int{6, 7, 8} //creates and array and returns a slice reference
fmt.Println(c)
}
在上面的案例中,c := []int{6, 7, 8}
創(chuàng)建了一個(gè)長度為 3 的 int 數(shù)組买决,并返回一個(gè)切片給 c沛婴。
修改切片
切片本身不包含任何數(shù)據(jù)吼畏。它僅僅是底層數(shù)組的一個(gè)上層表示。對(duì)切片進(jìn)行的任何修改都將反映在底層數(shù)組中嘁灯。
func main() {
arr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
slice := arr[2:5]
fmt.Println("array before",arr)
for i := range slice {
slice[i]++
}
fmt.Println("array after",arr)
}
我們創(chuàng)建了一個(gè)從 arr[2]
到 arr[5]
的切片 slice
泻蚊。for
循環(huán)將這些元素值加 1
。執(zhí)行完 for
語句之后打印原數(shù)組的值丑婿,我們可以看到原數(shù)組的值被改變了性雄。程序輸出如下:
array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]
當(dāng)若干個(gè)切片共享同一個(gè)底層數(shù)組時(shí),對(duì)每一個(gè)切片的修改都會(huì)反映在底層數(shù)組中羹奉。
func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //creates a slice which contains all elements of the array
nums2 := numa[:]
fmt.Println("array before change 1", numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
}
numa[:]
中缺少了開始和結(jié)束的索引值秒旋,這種情況下開始和結(jié)束的索引值默認(rèn)為 0
和len(numa)
。這里 nums1
和 nums2
共享了同一個(gè)數(shù)組诀拭。輸出為:
array before change 1 [78 79 80]
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]
切片的長度和容量
切片的長度是指切片中元素的個(gè)數(shù)迁筛。切片的容量是指從切片的起始元素開始到其底層數(shù)組中的最后一個(gè)元素的個(gè)數(shù)。(使用內(nèi)置函數(shù) cap 返回切片的容量
)
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice))
}
在上面的案例中耕挨,創(chuàng)建了一個(gè)以 fruitarray
為底層數(shù)組细卧,索引從 1
到 3
的切片 fruitslice
。因此 fruitslice
長度為2
俗孝。
fruitarray
的長度是 7酒甸。fruiteslice
是從 fruitarray
的索引 1
開始的。因此 fruiteslice
的容量是從 fruitarray
的第 1
個(gè)元素開始算起的數(shù)組中的元素個(gè)數(shù)赋铝,這個(gè)值是 6
插勤。因此 fruitslice
的容量是 6
。輸出為:length of slice 2 capacity 6
革骨。
切片的長度可以動(dòng)態(tài)的改變(最大為其容量)农尖。任何超出最大容量的操作都會(huì)發(fā)生運(yùn)行時(shí)錯(cuò)誤。
func main() {
fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
fruitslice = fruitslice[:cap(fruitslice)] //re-slicing furitslice till its capacity
fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice))
}
在上面的案例中良哲, 修改 fruitslice
的長度為它的容量盛卡。輸出如下:
length of slice 2 capacity 6
After re-slicing length is 6 and capacity is 6
用 make 創(chuàng)建切片
內(nèi)置函數(shù) func make([]T, len, cap) []T 可以用來創(chuàng)建切片,該函數(shù)接受長度和容量作為參數(shù)筑凫,返回切片滑沧。容量是可選的,默認(rèn)與長度相同巍实。使用 make 函數(shù)將會(huì)創(chuàng)建一個(gè)數(shù)組并返回它的切片滓技。
func main() {
i := make([]int, 5, 5)
fmt.Println(i)
}
用 make
創(chuàng)建的切片的元素值默認(rèn)為 0 值。上面的程序輸出為:[0 0 0 0 0]
棚潦。
追加元素到切片
我們已經(jīng)知道數(shù)組是固定長度的庐冯,它們的長度不能動(dòng)態(tài)增加仑最。而切片是動(dòng)態(tài)的酬核,可以使用內(nèi)置函數(shù) append 添加元素到切片盅惜。append 的函數(shù)原型為:append(s []T, x ...T) []T。
x …T 表示 append
函數(shù)可以接受的參數(shù)個(gè)數(shù)是可變的。這種函數(shù)叫做變參函數(shù)。
你可能會(huì)問一個(gè)問題:如果切片是建立在數(shù)組之上的,而數(shù)組本身不能改變長度收叶,那么切片是如何動(dòng)態(tài)改變長度的呢?實(shí)際發(fā)生的情況是玄组,當(dāng)新元素通過調(diào)用 append 函數(shù)追加到切片末尾時(shí)滔驾,如果超出了容量,append 內(nèi)部會(huì)創(chuàng)建一個(gè)新的數(shù)組俄讹。并將原有數(shù)組的元素被拷貝給這個(gè)新的數(shù)組哆致,最后返回建立在這個(gè)新數(shù)組上的切片。這個(gè)新切片的容量是舊切片的二倍(當(dāng)超出切片的容量時(shí)患膛,append 將會(huì)在其內(nèi)部創(chuàng)建新的數(shù)組摊阀,該數(shù)組的大小是原切片容量的 2 倍。最后 append 返回這個(gè)數(shù)組的全切片踪蹬,即從 0 到 length - 1 的切片
)胞此。下面的程序使事情變得明朗:
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
的容量開始時(shí)為 3跃捣。我們追加了一個(gè)新的元素給 cars
漱牵,并將 append(cars, "Toyota")
的返回值重新復(fù)制給 cars
。現(xiàn)在 cars
的容量翻倍疚漆,變?yōu)?6酣胀。上面的程序輸出為:
cars: [Ferrari Honda Ford] has old length 3 and capacity 3
cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6
切片的 0 值為 nil。一個(gè) nil 切片的長度和容量都為 0娶聘∥畔猓可以利用 append 函數(shù)給一個(gè) nil 切片追加值。
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)
}
}
在上面的程序中 names
為 nil
丸升,并且我們把 3 個(gè)字符串追加給 names
铆农。輸出為:
slice is nil going to append
names contents: [John Sebastian Vinay]
可以使用 ...
操作符將一個(gè)切片追加到另一個(gè)切片末尾:
func main() {
veggies := []string{"potatoes", "tomatoes", "brinjal"}
fruits := []string{"oranges", "apples"}
food := append(veggies, fruits...)
fmt.Println("food:", food)
}
上面的程序中,在第10行將 fruits
追加到 veggies
并賦值給 food
狡耻。...
操作符用來展開切片墩剖。程序的輸出為:food: [potatoes tomatoes brinjal oranges apples]
。
切片作為函數(shù)參數(shù)
可以認(rèn)為切片在內(nèi)部表示為如下的結(jié)構(gòu)體:
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
可以看到切片包含長度夷狰、容量涛碑、以及一個(gè)指向首元素的指針。當(dāng)將一個(gè)切片作為參數(shù)傳遞給一個(gè)函數(shù)時(shí)孵淘,雖然是值傳遞,但是指針始終指向同一個(gè)數(shù)組歹篓。因此將切片作為參數(shù)傳給函數(shù)時(shí)瘫证,函數(shù)對(duì)該切片的修改在函數(shù)外部也可以看到揉阎。讓我們寫一個(gè)程序來驗(yàn)證這一點(diǎn)。
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
func main() {
nos := []int{8, 7, 6}
fmt.Println("slice before function call", nos)
subtactOne(nos) //function modifies the slice
fmt.Println("slice after function call", nos) //modifications are visible outside
}
在上面的程序中背捌,將切片中的每個(gè)元素的值減2
毙籽。在函數(shù)調(diào)用之后打印切片的的內(nèi)容,發(fā)現(xiàn)切片內(nèi)容發(fā)生了改變毡庆。你可以回想一下坑赡,這不同于一個(gè)數(shù)組,對(duì)函數(shù)內(nèi)部的數(shù)組所做的更改在函數(shù)外不可見么抗。上面的程序輸出如下:
slice before function call [8 7 6]
slice after function call [6 5 4]
多維切片
同數(shù)組一樣毅否,切片也可以有多個(gè)維度。
func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
輸出:
C C++
JavaScript
Go Rust
內(nèi)存優(yōu)化
切片保留對(duì)底層數(shù)組的引用蝇刀。只要切片存在于內(nèi)存中螟加,數(shù)組就不能被垃圾回收。這在內(nèi)存管理方便可能是值得關(guān)注的吞琐。假設(shè)我們有一個(gè)非常大的數(shù)組捆探,而我們只需要處理它的一小部分,為此我們創(chuàng)建這個(gè)數(shù)組的一個(gè)切片站粟,并處理這個(gè)切片黍图。這里要注意的事情是,數(shù)組仍然存在于內(nèi)存中奴烙,因?yàn)榍衅谝盟?/p>
解決該問題的一個(gè)方法是使用 copy 函數(shù) func copy(dst, src []T) int
來創(chuàng)建該切片的一個(gè)拷貝助被。這樣我們就可以使用這個(gè)新的切片,原來的數(shù)組可以被垃圾回收缸沃。
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)
}
在上面程序中恰起,neededCountries := countries[:len(countries)-2]
創(chuàng)建一個(gè)底層數(shù)組為 countries
并排除最后兩個(gè)元素的切片。將 neededCountries
拷貝到 countriesCpy
并在下一行返回 countriesCpy
≈耗粒現(xiàn)在數(shù)組countries
可以被垃圾回收检盼,因?yàn)?neededCountries
不再被引用。