1 數(shù)組
1.1 如何定義數(shù)組
數(shù)組是具有相同類型且長(zhǎng)度固定的一組連續(xù)數(shù)據(jù)。在go語言中我們可以使用如下幾種方式來定義數(shù)組。
//方式一
var arr1 = [5]int{}
//方式二
var arr2 = [5]int{1,2,3,4,5}
//方式三
var arr3 = [5]int{3:10}
輸出以上三個(gè)變量的值如下所示:
arr1 [0 0 0 0 0]
arr2 [1 2 3 4 5]
arr3 [0 0 0 10 0]
- 方法一在聲明時(shí)沒有為其指定初值,所以數(shù)組內(nèi)的值被初始化為類型的零值。
- 方法二使用顯示的方式為數(shù)組定義初值。
- 方法三通過下標(biāo)的方式為下標(biāo)為3的位置賦上了初值10
而且在數(shù)組的定義是包含其長(zhǎng)度的,也就是說[3]int與[4]int是兩種不同的數(shù)據(jù)類型。
1.2 如何操作數(shù)據(jù)
在上面的arr1我們沒有為其指定初值,那么之后我們可以通過循環(huán)的方式為其賦予初值
for i := 0; i < len(arr1); i++ {
arr1[i] = i * 10
}
此時(shí)我們?cè)佥敵鲆幌聰?shù)組arr1,可以看到其值已經(jīng)變化
arr1 update: [0 10 20 30 40]
我們還可以通過循環(huán)的方式遍歷數(shù)組,與上面不同的是,for循環(huán)提供這樣的方式來為便利數(shù)組
for index, value := range arr1 {
fmt.Printf("index: %d, value: %d\n", index, value)
}
通過range來遍歷數(shù)組會(huì)有兩個(gè)返回值,其中第一個(gè)為數(shù)組的索引,第二個(gè)位置為對(duì)應(yīng)的值,輸出結(jié)果如下:
index: 0, value: 0
index: 1, value: 10
index: 2, value: 20
index: 3, value: 30
index: 4, value: 40
當(dāng)然不想要索引值也是可以的,可以用_代替。
1.3 多維數(shù)組
與其他語言一樣,go語言也可以定義多維數(shù)組,可以選擇的定義方式如下:
var arr4 = [5][5]int{
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
}
輸出當(dāng)前數(shù)組,可以看到數(shù)組內(nèi)的值如下所示
[[1 2 3 4 5] [6 7 8 9 10] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
1.4 數(shù)組作為函數(shù)參數(shù)
go語言在傳遞數(shù)組時(shí)會(huì)對(duì)其進(jìn)行拷貝,所以如果傳遞的是大數(shù)組的話會(huì)非常占內(nèi)存,所以一般情況下很少直接傳遞一個(gè)數(shù)組,避免這種情況我們可以使用以下兩種方式:
- 傳遞數(shù)組的指針
- 傳遞切片(具體內(nèi)容見下一小節(jié))
1.5 指針數(shù)組與數(shù)組指針
對(duì)于指針數(shù)組和數(shù)組指針在c或c++中也經(jīng)常被討論,尤其對(duì)于初學(xué)者來說會(huì)分辨不清楚。其實(shí)在每個(gè)詞中間添加一個(gè)“的“就很好理解了,指針的數(shù)組,數(shù)組的指針。
1.指針數(shù)組
對(duì)于指針數(shù)組來說,就是:一個(gè)數(shù)組里面裝的都是指針,在go語言中數(shù)組默認(rèn)是值傳遞的,所以如果我們?cè)诤瘮?shù)中修改傳遞過來的數(shù)組對(duì)原來的數(shù)組是沒有影響的。
func main() {
var a [5]int
fmt.Println(a)
test(a)
fmt.Println(a)
}
func test(a [5]int) {
a[1] = 2
fmt.Println(a)
}
輸出
[0 0 0 0 0]
[0 2 0 0 0]
[0 0 0 0 0]
但是如果我們一個(gè)函數(shù)傳遞的是指針數(shù)組,情況會(huì)有什么不一樣呢?
func main() {
var a [5]*int
fmt.Println(a)
for i := 0; i < 5; i++ {
temp := i
a[i] = &temp
}
for i := 0; i < 5; i++ {
fmt.Print(" ", *a[i])
}
fmt.Println()
test1(a)
for i := 0; i < 5; i++ {
fmt.Print(" ", *a[i])
}
}
func test1(a [5]*int) {
*a[1] = 2
for i := 0; i < 5; i++ {
fmt.Print(" ", *a[i])
}
fmt.Println()
}
我們先來看一下程序的運(yùn)行結(jié)果
[<nil> <nil> <nil> <nil> <nil>]
0 1 2 3 4
0 2 2 3 4
0 2 2 3 4
可以看到初始化值全是nil,也就驗(yàn)證了指針數(shù)組內(nèi)部全都是一個(gè)一個(gè)指針,之后我們將其初始化,內(nèi)部的每個(gè)指針指向一塊內(nèi)存空間。
注:在初始化的時(shí)候如果直接另a[i] = &i那么指針數(shù)組內(nèi)部存儲(chǔ)的全是同一個(gè)地址,所以輸出結(jié)果也一定是相同的
然后我們將這個(gè)指針數(shù)組傳遞給test1函數(shù),對(duì)于數(shù)組的參數(shù)傳遞仍然是復(fù)制的形式也就是值傳遞,但是因?yàn)閿?shù)組中每個(gè)元素是一個(gè)指針,所以test1函數(shù)復(fù)制的新數(shù)組中的值仍然是這些指針指向的具體地址值,這時(shí)改變a[1]這塊存儲(chǔ)空間地址指向的值,那么原實(shí)參指向的值也會(huì)變?yōu)?,具體流程如下圖所示。

2.數(shù)組指針
了解了指針數(shù)組之后,再來看一下數(shù)組指針,數(shù)組指針的全稱應(yīng)該叫做,指向數(shù)組的指針,在go語言中我們可以如下操作。
func main() {
var a [5]int
var aPtr *[5]int
aPtr = &a
//這樣簡(jiǎn)短定義也可以aPtr := &a
fmt.Println(aPtr)
test(aPtr)
fmt.Println(aPtr)
}
func test(aPtr *[5]int) {
aPtr[1] = 5
fmt.Println(aPtr)
}
我們先定義了一個(gè)數(shù)組a,然后定一個(gè)指向數(shù)組a的指針aPtr,然后將這個(gè)指針傳入一個(gè)函數(shù),在函數(shù)中我們改變了具體的值,程序的輸出結(jié)果
&[0 0 0 0 0]
&[0 5 0 0 0]
&[0 5 0 0 0]

通過上面的圖我們可以看見雖然main和test函數(shù)中的aPtr是不同的指針,但是他們都指向同一塊數(shù)組的內(nèi)存空間,所以無論在main函數(shù)還是在test函數(shù)中對(duì)數(shù)組的操作都會(huì)直接改變數(shù)組。
2 切片
因?yàn)閿?shù)組是固定長(zhǎng)度,所以在一些場(chǎng)合下就顯得不夠靈活,所以go語言提供了一種更為便捷的數(shù)據(jù)類型叫做切片。切片操作與數(shù)據(jù)類似,但是它的長(zhǎng)度是不固定的,可以追加元素,如果以達(dá)到當(dāng)前切片容量的上限會(huì)再自動(dòng)擴(kuò)容。
2.1 如何定義切片
可以通過以下幾種方式定義切片
package main
import "fmt"
func main() {
//方法一:聲明了一個(gè)空切片
var s1 = []int{}
//方法二:聲明了一個(gè)長(zhǎng)度為3的切片
var s2 = []int{1, 2, 3}
//方法三:聲明了一個(gè)長(zhǎng)度為5的空切片
var s3 = make([]int, 5)
//方法四:聲明了一個(gè)長(zhǎng)度為5容量為10的切片
var s4 = make([]int, 5, 10)
//可以看到切片的定義與數(shù)組類似,但是定義切片不需要為其指定長(zhǎng)度。
//我們可以通過len()和cap()這兩個(gè)函數(shù)來獲取切片的長(zhǎng)度和容量,下面就來依次看下上面各切片的長(zhǎng)度以及容量。
fmt.Println("s1", s1, len(s1), cap(s1))
fmt.Println("s2", s2, len(s2), cap(s2))
fmt.Println("s3", s3, len(s3), cap(s3))
fmt.Println("s4", s4, len(s4), cap(s4))
//如果我們通過這種方式定義一個(gè)切片,那么他會(huì)被賦予切片的空值nil。
var s5 []int
fmt.Println("s5", s5, len(s5), cap(s5))
}
執(zhí)行結(jié)果
s1 [] 0 0
s2 [1 2 3] 3 3
s3 [0 0 0 0 0] 5 5
s4 [0 0 0 0 0] 5 10
s5 [] 0 0
切片操作
我們可以通過如下的方式在數(shù)組和切片上繼續(xù)獲取切片
package main
import "fmt"
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := []int{6, 7, 8, 9, 10}
s1 := arr[2:4] //起始位0,從第2位到第4位前
s2 := arr[:3] //從起始位0,到第3位前
s3 := arr[2:] //從第2位到終了位
s4 := s[1:3] //從第1位到第3位前
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("s4:", s4)
}
執(zhí)行結(jié)果
s1: [3 4]
s2: [1 2 3]
s3: [3 4 5]
s4: [7 8]
可以看到切片操作是“包左不包右”,例如arr[2:4]是選擇arr數(shù)組內(nèi)下標(biāo)為2,3的兩個(gè)元素。如果:左邊沒有起始位置則默認(rèn)從頭開始,同理如果右邊沒有終止位置則默認(rèn)到末尾。
2.2 切片的擴(kuò)充與拼接
我們之前介紹了切片的定義以及一些切片的操作,下面就來看一下如何對(duì)切片進(jìn)行擴(kuò)充。
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := a[1:3]
b = append(b, 4)
b = append(b, 5)
b = append(b, 6)
b = append(b, 7)
fmt.Println(a)
fmt.Println(b)
}
執(zhí)行結(jié)果
[1 2 3]
[2 3 4 5 6 7]
如果想要將兩個(gè)切片進(jìn)行拼接可以使用如下這種方式:
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := a[1:3]
fmt.Println(b)
a = append(a, b...)//兩個(gè)切片,關(guān)鍵...
fmt.Println(a)
}
執(zhí)行結(jié)果
[2 3]
[1 2 3 2 3]
如果我們想要將一個(gè)切片的值復(fù)制給另一個(gè)切片,go語言也提供了非常簡(jiǎn)便的方式。
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := make([]int, 6)
fmt.Println(a)
fmt.Println(b)
copy(b, a) //copy(拷貝先,拷貝元)
fmt.Println(a)
fmt.Println(b)
}
執(zhí)行結(jié)果
[1 2 3]
[0 0 0 0 0 0]
[1 2 3]
[1 2 3 0 0 0]
你不妨動(dòng)手試一試下面幾個(gè)操作結(jié)果會(huì)怎么樣:
聲明b切片時(shí),其長(zhǎng)度比a切片長(zhǎng),復(fù)制結(jié)果是怎么樣的?
聲明b切片時(shí),其長(zhǎng)度比a切片短,復(fù)制結(jié)果是怎么樣的?
聲明b切片時(shí),其長(zhǎng)度被定義為0,那么調(diào)用copy函數(shù)會(huì)報(bào)錯(cuò)嗎?
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := make([]int, 6)
c := make([]int, 2)
d := make([]int, 0)
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
copy(b, a) //copy(拷貝先,拷貝元) 長(zhǎng)
copy(c, a) //copy(拷貝先,拷貝元) 短
copy(d, a) //copy(拷貝先,拷貝元) 0
fmt.Println("a:", a)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("d:", d)
}
執(zhí)行結(jié)果
[1 2 3]
[0 0 0 0 0 0]
[0 0]
[]
a: [1 2 3]
b: [1 2 3 0 0 0]
c: [1 2]
d: []
結(jié)合append與copy函數(shù)可以實(shí)現(xiàn)很多非常實(shí)用的功能,你也可以動(dòng)手嘗試一下??
刪除處于索引i的元素
在索引i的位置插入元素
在索引i的位置插入長(zhǎng)度為 j 的新切片
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
i := 1
//刪除處于索引i的元素
fmt.Println("刪除前:", a)
b := a[:i]
c := a[i+1:]
fmt.Println(append(b, c...))
fmt.Println("刪除后:", a)
//在索引i的位置插入元素
aa := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
fmt.Println("插入元素前:", aa)
d := aa[:i] //1
e := aa[i:] //2,3,4,5,6,7,8,9,0
ch := make([]int, len(d)+1) //
copy(ch, d)
ch[i] = 10
f := append(ch, e...)
fmt.Println("插入元素后:", f)
//在索引i的位置插入長(zhǎng)度為j的新切片
aaa := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
ins := []int{11, 22}
fmt.Println("插入元素前:", aaa)
dd := aaa[:i] //1
ee := aaa[i:] //2,3,4,5,6,7,8,9,0
chh := make([]int, len(dd))
copy(chh, dd)
chhh := append(chh, ins...)
ff := append(chhh, ee...)
fmt.Println("插入元素后:", ff)
}
等等
刪除前: [1 2 3 4 5 6 7 8 9 0]
[1 3 4 5 6 7 8 9 0]
刪除后: [1 3 4 5 6 7 8 9 0 0]
插入元素前: [1 2 3 4 5 6 7 8 9 0]
插入元素后: [1 10 2 3 4 5 6 7 8 9 0]
插入元素前: [1 2 3 4 5 6 7 8 9 0]
插入元素后: [1 11 22 2 3 4 5 6 7 8 9 0]
2.3 切片與數(shù)組的關(guān)系
對(duì)于任何一個(gè)切片來說,其都有一個(gè)底層數(shù)組與之對(duì)應(yīng),我們可以將切片看作是一個(gè)窗口,透過這個(gè)窗口可以看到底層數(shù)組的一部分元素,對(duì)應(yīng)關(guān)系如下圖所示。

下面有幾個(gè)問題歡迎你自己動(dòng)手嘗試一下:
編寫程序看看切片的容量與數(shù)組的大小有什么關(guān)系呢?
如果我們?cè)谇衅显僮銮衅敲此麄儠?huì)指向相同的底層數(shù)組嗎?修改其中一個(gè)切片會(huì)影響其他切片的值么?其中一個(gè)切片擴(kuò)容到容量大小之后會(huì)更換底層數(shù)組,那么之前的其他切片也會(huì)指向新的底層數(shù)組嗎?
既然切片是引用底層數(shù)組的,需要注意的就是小切片引用大數(shù)組的問題,如果底層的大數(shù)組一直有切片進(jìn)行引用,那么垃圾回收機(jī)制就不會(huì)將其收回,造成內(nèi)存的浪費(fèi),最有效的做法是copy需要的數(shù)據(jù)后再進(jìn)行操作