本位主要介紹一下 Go 語(yǔ)言中可變長(zhǎng)度的"數(shù)組"——切片(slice)。數(shù)組有數(shù)組的用處,但是其不可變長(zhǎng)度的特性,注定了在大多場(chǎng)景下不是很受歡迎。在大多數(shù)場(chǎng)景下我們都會(huì)選擇更加靈活的切片。
1. 切片的創(chuàng)建
切片的聲明方式和數(shù)組類似,寫(xiě)法上看就是聲明一個(gè)沒(méi)有長(zhǎng)度的數(shù)組:var 切片名 []切片類型
。其中切片類型可以是切片本身,也就是切片的切片,就構(gòu)成了多維的切片。
切片在使用之前必須要初始化,它沒(méi)有零值。聲明后它的值是 nil,這是因?yàn)樗牡讓訉?shí)現(xiàn)是一個(gè)指向數(shù)組的指針,在你給它存入一個(gè)數(shù)組的地址之前,它只能是 nil。
代碼示例:
package main
import (
"fmt"
)
func main() {
var a []int
fmt.Println("初始化前:", a)
a = make([]int, 5, 10)
fmt.Println("初始化后:", a)
a[4] = 5
fmt.Println(" 賦值后:", a)
a[5] = 6
fmt.Println("賦值后:", a)
}
- 第 8 行:聲明一個(gè)int類型的切片。
- 第 10 行:聲明一個(gè)長(zhǎng)度為5,切片容量為10的切片。其中容量可以不傳,默認(rèn)會(huì)和長(zhǎng)度相等。長(zhǎng)度為切片真正有值的位置,會(huì)初始化零值。
- 第 12 行:給切片的第 5 個(gè)位置賦值。
- 第 14 行:給切片的第 6 個(gè)位置賦值,但是切片的長(zhǎng)度為5,所以會(huì)報(bào)越界的錯(cuò)誤。
執(zhí)行結(jié)果:
2. 切片的截取
切片之所以被叫做切片是有原因的,它可以從任意長(zhǎng)度開(kāi)始切,切到任意長(zhǎng)度為止,然后這一段拿出來(lái)就是一個(gè)新的切片。切割形式為切片名(s)[起始下標(biāo)(begin):結(jié)束下標(biāo)(end):最大容量(max)]
。
Tips:截取到的切片包含起始下標(biāo)(begin),不包含結(jié)束下標(biāo)(end)。
切片截取形式表
操作 | 含義 |
---|---|
s[begin?max] | 截取切片s從begin到end的數(shù)據(jù),構(gòu)成一個(gè)容量為max-begin,長(zhǎng)度為begin-end的切片。(用的不多) |
s[begin:end] | 截取切片s從begin到end的數(shù)據(jù),構(gòu)成一個(gè)容量和長(zhǎng)度均為begin-end的切片。 |
s[begin:] | 截取切片s從begin到最后的數(shù)據(jù),構(gòu)成一個(gè)容量和長(zhǎng)度均為len(s)-end的切片。 |
s[:end] | 截取切片s從0到最后的數(shù)據(jù),構(gòu)成一個(gè)容量和長(zhǎng)度均為end-0的切片。 |
代碼示例:
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
fmt.Println("a[1:3]=", a[1:3])
fmt.Println("a[1:]=", a[1:])
fmt.Println("a[:3]=", a[:3])
}
- 第 8 行:直接定義一個(gè)值為
[1,2,3,4,5]
的切片,切片長(zhǎng)度和容量會(huì)根據(jù)切片的值自動(dòng)生成。例如本行代碼定義的切片就是長(zhǎng)度和容量均為5。 - 第 9 行:取切片下標(biāo)從1開(kāi)始到3之前的值,生成新切片。
- 第 10 行:取切片下標(biāo)從1開(kāi)始到最后的值,生成新切片。
- 第 11 行:取切片下標(biāo)從0開(kāi)始到3的值,生成新切片。
執(zhí)行結(jié)果:
3. 切片的追加
切片使用一個(gè) Go 語(yǔ)言的內(nèi)置函數(shù)append(切片,待添加的值)
,來(lái)進(jìn)行切片末尾元素的追加。
代碼示例:
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
a = append(a, 6)
fmt.Println(a)
a = append(a, 7, 8)
fmt.Println(a)
b := []int{9, 10}
a = append(a, b...)
fmt.Println(a)
}
- 第 9 行:在切片 a 的末尾追加一個(gè)元素 6。
- 第 11 行:在切片 a 的末尾連續(xù)追加兩個(gè)元素 7 和 8。append 中待添加的值可以是 多個(gè),其中使用
,
隔開(kāi)。 - 第 14 行:在切片 a 的末尾追加切片 b。當(dāng) append 中待添加的元素是一個(gè)數(shù)組或者切片時(shí),在其后面添加
...
就可以全部追加到切片末尾。
執(zhí)行結(jié)果:
4. 切片的長(zhǎng)度和容量
在切片中可以使用len()
獲取切片中元素的數(shù)量,也就是切片的長(zhǎng)度。使用cap()
可以獲取切片引用的數(shù)組的長(zhǎng)度,也就切片的容量。切片的容量一般大于等于長(zhǎng)度,容量會(huì)隨著長(zhǎng)度的增長(zhǎng)而增長(zhǎng)。
在初始化一個(gè)切片的時(shí)候其實(shí)時(shí)給切片引用了一個(gè)數(shù)組,然后容量就是這個(gè)數(shù)組的長(zhǎng)度,然后如果切片的長(zhǎng)度超過(guò)了切片的容量,它就會(huì)讓切片引用一個(gè)容量更大數(shù)組來(lái)存放這些元素。
package main
import (
"fmt"
)
func main() {
var a = []int{1, 2, 3, 4, 5}
fmt.Printf("a的地址%p,a的長(zhǎng)度%d,a的容量%d\n", a, len(a), cap(a))
a = append(a, 6)
fmt.Printf("a的地址%p,a的長(zhǎng)度%d,a的容量%d\n", a, len(a), cap(a))
a = append(a, 7, 8)
fmt.Printf("a的地址%p,a的長(zhǎng)度%d,a的容量%d\n", a, len(a), cap(a))
b := []int{9, 10, 11}
a = append(a, b...)
fmt.Printf("a的地址%p,a的長(zhǎng)度%d,a的容量%d\n", a, len(a), cap(a))
}
執(zhí)行結(jié)果:
從執(zhí)行結(jié)果可以看到,在切片a每次添加的元素要超過(guò)它的容量時(shí),它的地址就會(huì)發(fā)生改變,其實(shí)就是讓它引用了一個(gè)新的容量更大的數(shù)組。
5. 小結(jié)
本文主要介紹了切片的使用,有以下注意事項(xiàng):
- 切片在使用前需要初始化;
- 切片的本質(zhì)是一個(gè)指針數(shù)組,但是它的地址會(huì)隨著長(zhǎng)度超過(guò)容量而改變;
- 在應(yīng)用場(chǎng)景中一般都使用切片。