Go語言系列(三)之陣列和切片

行人觀學發表於2020-08-01

《Go語言系列文章》

  1. Go語言系列(一)之Go的安裝和使用
  2. Go語言系列(二)之基礎語法總結

1. 陣列

陣列用於儲存若干個相同型別的變數的集合。陣列中每個變數稱為陣列的元素,每個元素都有一個數字編號——陣列下標,該下標從0開始,用於區別各個元素。陣列中可容納的元素個數稱為陣列的長度

1.1. 宣告

Go語言中陣列的宣告方式:

var arr_name [length]type

var:不必多說,宣告變數時都會用到該關鍵字。

arr_name:陣列名稱,本質是個變數

length:陣列的長度

type:陣列的型別

[]:通過它來進行對陣列元素的讀取、賦值

下面是一個例子:

package main

import "fmt"

func main() {
    var a [2]string //宣告一個長度為2的string陣列
    a[0] = "我是"	   //賦值
    a[1] = "行小觀"
    fmt.Println(a[0], a[1]) //獲取元素
    fmt.Println(a)
}

1.2. 初始化

《Go語言系列(二)之基礎語法總結》這篇文章中提過:若我們在宣告變數時,不給變數賦初始值,則這些變數會被賦予“零值”。

陣列中也是這樣,如果不初始化,則陣列中的所有元素值都為“零值”。如下例:

package main

import "fmt"

func main() {
    var a [3]int
    var b [3]string
    var c [3]bool

    fmt.Println(a) //[0 0 0]
    fmt.Println(b) //[  ]
    fmt.Println(c) //[false false false]
}

對陣列元素進行初始化:

package main

import "fmt"

func main() {
    var a = [5]int {1, 2, 3}
    fmt.Println(a) //[1 2 3 0 0]
}

只初始化了部分元素,剩餘的仍是零值。

如果我們在宣告陣列時同時初始化了,可以使用...而不指定陣列的長度,Go會自動計算陣列長度:

var a = [...]int {1, 2, 3} //初始化,陣列長度為3

1.3. 短變數方式宣告

當然,我們可以使用短變數宣告的方式宣告陣列。注意:使用該方式就必須在宣告的時候同時初始化

如果你只是想使用這種方式來宣告一個陣列,但並不初始化,可以這樣做,但是必須帶上{}

package main

import "fmt"

func main() {
	a := [5]int {1, 2, 3} //初始化
    b := [3]int {}
    c := [...]int {1, 2, 3}

	fmt.Println(a) //[1 2 3 0 0]
   	fmt.Println(b) //[0 0 0]
    fmt.Println(c) //[1 2 3]
}

1.4. 特殊之處

注意:在Go語言中,陣列的長度是其型別的一部分。 所以Go中的陣列不能改變長度。

怎麼理解?下面宣告瞭兩個陣列:

var a [4]int //將變數a宣告為擁有4個整數的陣列

var b [5]int //將變數b宣告為擁有5個整數的陣列

變數ab 的型別分別為[4]int[5]int,是不同的型別。

1.5. 二維陣列

二維陣列當中的元素仍是陣列:

var ab = [2][4]int {[4]int {1, 2, 3, 4}, [4]int {4, 5, 6, 7}}
或
ab := [2][4]int {[4]int {1, 2, 3, 4}, [4]int {4, 5, 6, 7}}

可以省去陣列元素的型別:

var ab = [2][4]int {{1, 2, 3, 4}, {4, 5, 6, 7}}
或
ab := [2][4]int {{1, 2, 3, 4}, {4, 5, 6, 7}}

1.6. 遍歷陣列

(一)使用陣列長度

可以使用len(slice)函式獲取陣列長度,然後遍歷。

arr := [5]string {"a", "b", "c", "d", "e"}

bc := [2][4]int {
    {1, 2, 3, 4}, 
    {5, 6, 7, 8},
}

for i := 0; i < len(arr); i++ {//遍歷一維陣列
    fmt.Println(arr[i])
}

for i := 0; i < len(bc); i++ {//遍歷二維陣列的元素
    fmt.Println(bc[i])
}

for i := 0; i < len(bc); i++ {//遍歷二維陣列的元素的元素
    for j := 0; j < len(bc[0]); j++ {
        fmt.Println(bc[i][j])
    }
}

(二)使用range關鍵字

range關鍵字用於for迴圈中遍歷陣列時,每次迭代都會返回兩個值,第一個值為當前元素的下標,第二值為該下標所對應的元素值。如果這兩個值的其中一個你不需要,只需使用下劃線_代替即可。

arr := [5]string {"a", "b", "c", "d", "e"}

bc := [2][4]int {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
}

for i, v := range arr {//遍歷一維陣列
    fmt.Println(i, v)
}

for i := range arr {//遍歷時只獲取下標
    fmt.Println(i)
}

for _, v := range arr{//遍歷時只獲取元素值
    fmt.Println(v)
}

for _, v := range bc {//遍歷二維陣列
    for _, w := range v{
        fmt.Println(w)
    }
}

2. 切片(slice)

前面提到:Go中的陣列的長度是固定的。這樣就會在實際應用中帶來不方便,因為很多時候在宣告陣列前並不明確該陣列要儲存多少個元素。宣告太多,浪費;宣告太少,不夠用。

而切片就為我們提供了“動態陣列”。

2.1. 使用

宣告切片和宣告陣列類似,但是不指定長度

var sli_name []type

比如,宣告一個int型別、名為a的切片:

var a []int

可以在宣告它的時候直接初始化:

var a = []int {1, 2, 3, 4}

當然,也可以使用短變數的方式宣告:

a := []int {1, 2, 3, 4}

可以從一個已有的陣列或者已有的切片中獲取切片。

獲取切片的方式是通過兩個下標來獲取,即開始下標(startIndex)和結束下標(endIndex),二者以冒號分隔。包括startIndex,不包括endIndex

a[startIndex : endIndex]

下面是一個例子:

a := [5]string {"a", "b", "c", "d", "e"} //陣列
b := []int {1, 2, 3, 4} //切片

sliA := a[2:4]
sliB := b[1:3]

fmt.Println(sliA) //[c d]
fmt.Println(sliB) //[2 3]

2.2. 切片與陣列

前面提到:切片為我們提供了“動態陣列”。但該“動態陣列”並不是真正意義上的能擴充套件長度的動態陣列。

切片並不儲存任何資料,它只是一個引用型別,切片總是指向一個底層的陣列,描述這個底層陣列的一段。

所以我們在宣告陣列時需要指定長度,而宣告切片時不需要:

var arr = [4]int {1, 2, 3, 4} //宣告陣列

var slice = []int {1, 2, 3, 4} //宣告切片

由於切片的底層引用的是陣列,所以更改切片中的元素會修改其底層陣列中對應的元素,如果還有其他切片也引用了該底層陣列,那麼這些切片也能觀測到這些修改。如圖:

下面是一個例子:

package main

import "fmt"

func main() {
	array := [5]string {"aa", "bb", "cc", "dd", "ee"} //陣列
	fmt.Println(array) //[aa bb cc dd ee]

	slice1 := array[0:2] //切片1
	slice2 := array[1:3] //切片2
	slice3 := array[2:5] //切片3

	fmt.Println(slice1) //[aa bb]
	fmt.Println(slice2) //[bb cc]
	fmt.Println(slice3) //[cc dd ee]

	slice1[0] = "xx" //修改切片1中的值
	slice2[1] = "yy" //修改切片2中的值
	slice3[2] = "zz" ////修改切片3中的值

	fmt.Println(array) //[xx bb yy dd zz]
	fmt.Println(slice1) //[xx bb]
	fmt.Println(slice2) //[bb yy]
	fmt.Println(slice3) //[yy dd zz]
}

2.3. 切片的相關操作

(一)長度

切片的長度指切片所包含的元素個數。通過函式len(s)獲取切片s的長度。

(二)容量

切片的容量指切片的第一個元素到其底層陣列的最後一個元素的個數。通過函式cap(s)獲取切片s的容量。

下面是一個例子:

package main

import "fmt"

func main() {
	arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
	s := arr[2:5] //建立切片s

	fmt.Println(arr) //[a b c d e f g h i j]
	fmt.Println(s) //[c d e]

	fmt.Println(len(s)) //3
	fmt.Println(cap(s)) //8
}

下面是長度和容量的示意圖:

有了容量這個概念,我們就可以通過重新切片來改變切片的長度:

package main

import "fmt"

func main() {
	arr := [10]string {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
	s := arr[2:5]
	fmt.Printf("s的長度為%d,s的容量為%d\n", len(s), cap(s))
	s = s[2:8]
	fmt.Printf("s的長度為%d,s的容量為%d\n", len(s), cap(s))
	s = s[0:2]
	fmt.Printf("s的長度為%d,s的容量為%d\n", len(s), cap(s))
}

(三)追加元素

使用func append(slice []Type, elems ...Type) []Type可以向切片slice的末尾追加型別為Type的元素elems

該函式的結果是一個包含原切片所有元素加上新新增元素的切片。由於改變切片內容了,所以底層陣列也會被改變。

package main

import "fmt"

func main() {
	s := []string {"a", "b", "c", "d"}
	s = append(s, "e") //追加1個
	s = append(s, "f", "g", "h") //追加3個
	fmt.Println(s)
}

當切片中容量已經用完時(len(s) == cap(s)),也即底層陣列容納不了追加的元素時,Go會分配一個更大的底層陣列,返回的切片指向這個新分配的陣列,原陣列的內容不變。

package main

import "fmt"

func main() {
	arr := [5]string {"a", "b", "c", "d", "e"}
	slice1 := arr[0:2]
	fmt.Println(slice1) //[a b]
	//追加3個元素,slice1的容量已滿
	slice1 = append(slice1, "1", "2", "3")
	fmt.Println(slice1) //[a b 1 2 3]
    //底層陣列跟著改變
	fmt.Println(arr) //[a b 1 2 3]
	//繼續追加
	slice1 = append(slice1, "4", "5")
	//指向新的底層陣列
	fmt.Println(slice1) //[a b 1 2 3 4 5]
	//原底層陣列不變
	fmt.Println(arr) //[a b 1 2 3]
}

(四)複製切片

func copy(dst []Type, src []Type) int

dst是目標切片,src是源切片,該函式會將src中的元素複製到dst中,並返回複製的元素個數(該返回值是兩個切片長度中的小值)

package main

import "fmt"

func main() {
	slice1 := []string {"a", "b"}
	slice2 := []string {"1", "2", "3"}
	length := copy(slice2, slice1)
	//length := copy(slice1, slice2)
	fmt.Println(length)
	fmt.Println(slice1)
	fmt.Println(slice2)
}

(五)切片的預設行為

切片的預設開始下標是0,預設結束下標是切片的長度。

對於陣列:

var a [10]int

下面幾個切片是等價的:

a[0:10]
a[:10]
a[0:]
a[:]

2.4. 特殊切片

(一)nil切片

切片的零值是 nil,當宣告一個切片,但不出初始化它,該切片便為nil切片。nil切片的長度和容量為0且沒有底層陣列。

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("s切片是nil切片")
	}
}

(二)切片的切片

切片中的元素可以是切片

package main

import "fmt"

func main() {
	ss := [][]int {
		[]int {1, 2, 3}, //切片元素的型別可以省去
		[]int {4, 5, 6},
		[]int {7, 8, 9},
	}

	for i := 0; i < len(ss); i++ {
		fmt.Println(ss[i])
	}
}

2.5. 使用make函式建立切片

使用make函式可以在建立切片時指定長度和容量。make函式會分配一個元素為零值的陣列並返回一個引用了它的切片

該函式接受三個引數,分別用來指定切片的型別、長度、容量。當不傳入容量引數時,容量預設和長度相同。容量引數不能小於長度引數。

package main

import "fmt"

func main() {
	a := make([]int, 5)
	fmt.Println(a, len(a), cap(a)) //[0 0 0 0 0] 5 5

	b := make([]int, 5, 6)
	fmt.Println(b, len(b), cap(b)) //[0 0 0 0 0] 5 6

	//c := make([]int, 5, 4) 
	//fmt.Println(c, len(c), cap(c))//報錯:len larger than cap in make([]int)
}

2.6. 遍歷切片

因為切片是對陣列的引用,所以遍歷切片也就是在遍歷陣列。

3. 關於我

相關文章