切片(slice)是對(duì)數(shù)組一個(gè)連續(xù)片段的引用(該數(shù)組我們稱之為相關(guān)數(shù)組,通常是匿名的),所以切片是一個(gè)引用類型(因此更類似于 C/C++ 中的數(shù)組類型,或者 Python 中的 list 類型)。這個(gè)片段可以是整個(gè)數(shù)組,或者是由起始和終止索引標(biāo)識(shí)的一些項(xiàng)的子集。需要注意的是,終止索引標(biāo)識(shí)的項(xiàng)不包括在切片內(nèi)。切片提供了一個(gè)相關(guān)數(shù)組的動(dòng)態(tài)窗口。
切片是可索引的,并且可以由 len()
函數(shù)獲取長(zhǎng)度。
給定項(xiàng)的切片索引可能比相關(guān)數(shù)組的相同元素的索引小。和數(shù)組不同的是,切片的長(zhǎng)度可以在運(yùn)行時(shí)修改,最小為 0 最大為相關(guān)數(shù)組的長(zhǎng)度:切片是一個(gè) 長(zhǎng)度可變的數(shù)組。
切片提供了計(jì)算容量的函數(shù) cap()
可以測(cè)量切片最長(zhǎng)可以達(dá)到多少:它等于切片的長(zhǎng)度 + 數(shù)組除切片之外的長(zhǎng)度。如果 s 是一個(gè)切片,cap(s)
就是從 s[0]
到數(shù)組末尾的數(shù)組長(zhǎng)度。切片的長(zhǎng)度永遠(yuǎn)不會(huì)超過它的容量,所以對(duì)于 切片 s 來說該不等式永遠(yuǎn)成立:0 <= len(s) <= cap(s)
。
多個(gè)切片如果表示同一個(gè)數(shù)組的片段,它們可以共享數(shù)據(jù);因此一個(gè)切片和相關(guān)數(shù)組的其他切片是共享存儲(chǔ)的,相反,不同的數(shù)組總是代表不同的存儲(chǔ)。數(shù)組實(shí)際上是切片的構(gòu)建塊。
優(yōu)點(diǎn) 因?yàn)榍衅且?,所以它們不需要使用額外的內(nèi)存并且比使用數(shù)組更有效率,所以在 Go 代碼中 切片比數(shù)組更常用。
聲明切片的格式是: var identifier []type
(不需要說明長(zhǎng)度)。
一個(gè)切片在未初始化之前默認(rèn)為 nil,長(zhǎng)度為 0。
切片的初始化格式是:var slice1 []type = arr1[start:end]
。
這表示 slice1 是由數(shù)組 arr1 從 start 索引到 end-1
索引之間的元素構(gòu)成的子集(切分?jǐn)?shù)組,start:end 被稱為 slice 表達(dá)式)。所以 slice1[0]
就等于 arr1[start]
。這可以在 arr1 被填充前就定義好。
如果某個(gè)人寫:var slice1 []type = arr1[:]
那么 slice1 就等于完整的 arr1 數(shù)組(所以這種表示方式是 arr1[0:len(arr1)]
的一種縮寫)。另外一種表述方式是:slice1 = &arr1
。
arr1[2:]
和 arr1[2:len(arr1)]
相同,都包含了數(shù)組從第三個(gè)到最后的所有元素。
arr1[:3]
和 arr1[0:3]
相同,包含了從第一個(gè)到第三個(gè)元素(不包括第三個(gè))。
如果你想去掉 slice1 的最后一個(gè)元素,只要 slice1 = slice1[:len(slice1)-1]
。
一個(gè)由數(shù)字 1、2、3 組成的切片可以這么生成:s := [3]int{1,2,3}[:]
甚至更簡(jiǎn)單的 s := []int{1,2,3}
。
s2 := s[:]
是用切片組成的切片,擁有相同的元素,但是仍然指向相同的相關(guān)數(shù)組。
一個(gè)切片 s 可以這樣擴(kuò)展到它的大小上限:s = s[:cap(s)]
,如果再擴(kuò)大的話就會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤(參見第 7.7 節(jié))。
對(duì)于每一個(gè)切片(包括 string),以下狀態(tài)總是成立的:
s == s[:i] + s[i:] // i是一個(gè)整數(shù)且: 0 <= i <= len(s)
len(s) <= cap(s)
切片也可以用類似數(shù)組的方式初始化:var x = []int{2, 3, 5, 7, 11}
。這樣就創(chuàng)建了一個(gè)長(zhǎng)度為 5 的數(shù)組并且創(chuàng)建了一個(gè)相關(guān)切片。
切片在內(nèi)存中的組織方式實(shí)際上是一個(gè)有 3 個(gè)域的結(jié)構(gòu)體:指向相關(guān)數(shù)組的指針,切片長(zhǎng)度以及切片容量。下圖給出了一個(gè)長(zhǎng)度為 2,容量為 4 的切片y。
y[0] = 3
且 y[1] = 5
。y[0:4]
由 元素 3,5,7 和 11 組成。http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.2_fig7.2.png?raw=true" alt="" />
示例 7.7 array_slices.go
package main
import "fmt"
func main() {
var arr1 [6]int
var slice1 []int = arr1[2:5] // item at index 5 not included!
// load the array with integers: 0,1,2,3,4,5
for i := 0; i < len(arr1); i++ {
arr1[i] = i
}
// print the slice
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("The length of arr1 is %d\n", len(arr1))
fmt.Printf("The length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
// grow the slice
slice1 = slice1[0:4]
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("The length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
// grow the slice beyond capacity
//slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range
}
輸出:
Slice at 0 is 2
Slice at 1 is 3
Slice at 2 is 4
The length of arr1 is 6
The length of slice1 is 3
The capacity of slice1 is 4
Slice at 0 is 2
Slice at 1 is 3
Slice at 2 is 4
Slice at 3 is 5
The length of slice1 is 4
The capacity of slice1 is 4
如果 s2 是一個(gè) slice,你可以將 s2 向后移動(dòng)一位 s2 = s2[1:]
,但是末尾沒有移動(dòng)。切片只能向后移動(dòng),s2 = s2[-1:]
會(huì)導(dǎo)致編譯錯(cuò)誤。切片不能被重新分片以獲取數(shù)組的前一個(gè)元素。
注意 絕對(duì)不要用指針指向 slice。切片本身已經(jīng)是一個(gè)引用類型,所以它本身就是一個(gè)指針!!
問題 7.2: 給定切片 b:= []byte{'g', 'o', 'l', 'a', 'n', 'g'}
,那么 b[1:4]
、b[:2]
、b[2:]
和 b[:]
分別是什么?
如果你有一個(gè)函數(shù)需要對(duì)數(shù)組做操作,你可能總是需要把參數(shù)聲明為切片。當(dāng)你調(diào)用該函數(shù)時(shí),把數(shù)組分片,創(chuàng)建為一個(gè) 切片引用并傳遞給該函數(shù)。這里有一個(gè)計(jì)算數(shù)組元素和的方法:
func sum(a []int) int {
s := 0
for i := 0; i < len(a); i++ {
s += a[i]
}
return s
}
func main() {
var arr = [5]int{0, 1, 2, 3, 4}
sum(arr[:])
}
當(dāng)相關(guān)數(shù)組還沒有定義時(shí),我們可以使用 make() 函數(shù)來創(chuàng)建一個(gè)切片 同時(shí)創(chuàng)建好相關(guān)數(shù)組:var slice1 []type = make([]type, len)
。
也可以簡(jiǎn)寫為 slice1 := make([]type, len)
,這里 len
是數(shù)組的長(zhǎng)度并且也是 slice
的初始長(zhǎng)度。
所以定義 s2 := make([]int, 10)
,那么 cap(s2) == len(s2) == 10
。
make 接受 2 個(gè)參數(shù):元素的類型以及切片的元素個(gè)數(shù)。
如果你想創(chuàng)建一個(gè) slice1,它不占用整個(gè)數(shù)組,而只是占用以 len 為個(gè)數(shù)個(gè)項(xiàng),那么只要:slice1 := make([]type, len, cap)
。
make 的使用方式是:func make([]T, len, cap)
,其中 cap 是可選參數(shù)。
所以下面兩種方法可以生成相同的切片:
make([]int, 50, 100)
new([100]int)[0:50]
下圖描述了使用 make 方法生成的切片的內(nèi)存結(jié)構(gòu):http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.2_fig7.2.1.png?raw=true" alt="" />
示例 7.8 make_slice.go
package main
import "fmt"
func main() {
var slice1 []int = make([]int, 10)
// load the array/slice:
for i := 0; i < len(slice1); i++ {
slice1[i] = 5 * i
}
// print the slice:
for i := 0; i < len(slice1); i++ {
fmt.Printf("Slice at %d is %d\n", i, slice1[i])
}
fmt.Printf("\nThe length of slice1 is %d\n", len(slice1))
fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))
}
輸出:
Slice at 0 is 0
Slice at 1 is 5
Slice at 2 is 10
Slice at 3 is 15
Slice at 4 is 20
Slice at 5 is 25
Slice at 6 is 30
Slice at 7 is 35
Slice at 8 is 40
Slice at 9 is 45
The length of slice1 is 10
The capacity of slice1 is 10
因?yàn)樽址羌兇獠豢勺兊淖止?jié)數(shù)組,它們也可以被切分成 切片。
練習(xí) 7.4: fobinacci_funcarray.go: 為練習(xí) 7.3 寫一個(gè)新的版本,主函數(shù)調(diào)用一個(gè)使用序列個(gè)數(shù)作為參數(shù)的函數(shù),該函數(shù)返回一個(gè)大小為序列個(gè)數(shù)的 Fibonacci 切片。
看起來二者沒有什么區(qū)別,都在堆上分配內(nèi)存,但是它們的行為不同,適用于不同的類型。
&T{}
。換言之,new 函數(shù)分配內(nèi)存,make 函數(shù)初始化;下圖給出了區(qū)別:
http://wiki.jikexueyuan.com/project/the-way-to-go/images/7.3_fig7.3.png?raw=true" alt="" />
在圖 7.3 的第一幅圖中:
var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int)
在第二幅圖中, p := make([]int, 0)
,切片 已經(jīng)被初始化,但是指向一個(gè)空的數(shù)組。
以上兩種方式實(shí)用性都不高。下面的方法:
var v []int = make([]int, 10, 50)
或者
v := make([]int, 10, 50)
這樣分配一個(gè)有 50 個(gè) int 值的數(shù)組,并且創(chuàng)建了一個(gè)長(zhǎng)度為 10,容量為 50 的 切片 v,該 切片 指向數(shù)組的前 10 個(gè)元素。
問題 7.3 給定 s := make([]byte, 5)
,len(s) 和 cap(s) 分別是多少?s = s[2:4]
,len(s) 和 cap(s) 又分別是多少?
問題 7.4 假設(shè) s1 := []byte{'p', 'o', 'e', 'm'}
且 s2 := s1[2:]
,s2 的值是多少?如果我們執(zhí)行 s2[1] = 't'
,s1 和 s2 現(xiàn)在的值又分別是多少?
和數(shù)組一樣,切片通常也是一維的,但是也可以由一維組合成高維。通過分片的分片(或者切片的數(shù)組),長(zhǎng)度可以任意動(dòng)態(tài)變化,所以 Go 語(yǔ)言的多維切片可以任意切分。而且,內(nèi)層的切片必須單獨(dú)分配(通過 make 函數(shù))。
類型 []byte
的切片十分常見,Go 語(yǔ)言有一個(gè) bytes 包專門用來解決這種類型的操作方法。
bytes 包和字符串包十分類似(參見第 4.7 節(jié))。而且它還包含一個(gè)十分有用的類型 Buffer:
import "bytes"
type Buffer struct {
...
}
這是一個(gè)長(zhǎng)度可變的 bytes 的 buffer,提供 Read 和 Write 方法,因?yàn)樽x寫長(zhǎng)度未知的 bytes 最好使用 buffer。
Buffer 可以這樣定義:var buffer bytes.Buffer
。
或者使用 new 獲得一個(gè)指針:var r *bytes.Buffer = new(bytes.Buffer)
。
或者通過函數(shù):func NewBuffer(buf []byte) *Buffer
,創(chuàng)建一個(gè) Buffer 對(duì)象并且用 buf 初始化好;NewBuffer 最好用在從 buf 讀取的時(shí)候使用。
通過 buffer 串聯(lián)字符串
類似于 Java 的 StringBuilder 類。
在下面的代碼段中,我們創(chuàng)建一個(gè) buffer,通過 buffer.WriteString(s)
方法將字符串 s 追加到后面,最后再通過 buffer.String()
方法轉(zhuǎn)換為 string:
var buffer bytes.Buffer
for {
if s, ok := getNextString(); ok { //method getNextString() not shown here
buffer.WriteString(s)
} else {
break
}
}
fmt.Print(buffer.String(), "\n")
這種實(shí)現(xiàn)方式比使用 +=
要更節(jié)省內(nèi)存和 CPU,尤其是要串聯(lián)的字符串?dāng)?shù)目特別多的時(shí)候。
練習(xí) 7.5 給定切片 sl,將一個(gè) []byte
數(shù)組追加到 sl 后面。寫一個(gè)函數(shù) Append(slice, data []byte) []byte
,該函數(shù)在 sl 不能存儲(chǔ)更多數(shù)據(jù)的時(shí)候自動(dòng)擴(kuò)容。
練習(xí) 7.6 把一個(gè)緩存 buf 分片成兩個(gè) 切片:第一個(gè)是前 n 個(gè) bytes,后一個(gè)是剩余的,用一行代碼實(shí)現(xiàn)。