Go 數組&切片
數組相關
在Go
語言中,數組是一種容器相關的數據類型,用於存放多種相同類型的數據。
數組定義
在定義數組時,必須定義數組的類型以及長度,數組一經定義不可進行改變。
同時,數組的長度是按照元素個數進行統計的,並且數組長度是數組的一部分。
package main
import (
"fmt"
)
func main() {
var arr [10]string // 定義一個長度為10byte的數組
fmt.Println(len(arr)) // 10 數組長度
fmt.Println(cap(arr)) // 10 數組容量
}
定義賦值
數組可以在一經定義的時候就進行賦值,賦值的長度不可大於數組的長度。
數組也可以不定義長度,而是使用...
語法來動態計算填充的元素長度。
數組也可以在定義的時候通過索引對其進行賦值。
以下是定長的數組定義賦值
package main
import (
"fmt"
)
func main() {
var arr = [10]string{"①", "②", "③", "④","⑤","⑥","⑦","⑧","⑨","⑩"}
fmt.Printf("數組長度:%d\n", len(arr)) // 數組長度:10
fmt.Printf("數組容量:%d\n", cap(arr)) // 數組容量:10
fmt.Printf("第一個元素:%v\n", arr[0]) // 第一個元素:①
}
以下是不定長的數組定義賦值
package main
import (
"fmt"
)
func main() {
var arr = [...]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩", "拾壹"}
fmt.Printf("數組長度:%d\n", len(arr)) // 數組長度:11
fmt.Printf("數組容量:%d\n", cap(arr)) // 數組容量:11
fmt.Printf("最後一個元素:%v\n", arr[len(arr)-1]) // 最後一個元素:拾壹
}
以下是定長的數組索引賦值:
package main
import (
"fmt"
)
func main() {
var arr = [10]string{1: "②", 8: "⑨"}
fmt.Printf("數組長度:%d\n", len(arr)) // 數組長度:11
fmt.Printf("數組容量:%d\n", cap(arr)) // 數組容量:11
fmt.Printf("數組元素:%v\n", arr) // 數組元素:[ ② ⑨ ]
}
默認填充
當定義了一個數組並未填充數據時,會進行默認的數據填充:
布爾值是false
字符串是""
整形和浮點型都是0
多維數組
多維數組只有第一層可以使用...
來讓編譯器推導數組長度,其他層都需要手動指定長度。
如下定義了一個二維數組:
package main
import (
"fmt"
)
func main() {
// 一維數組:不定長 二維數組:定長,2個 在一維數組中創建了3個二維數組
var arr = [...][2]string{
{"1-1", "1-2"},
{"2-1", "2-2"},
{"3-1", "3-2"},
}
fmt.Println(arr) // [[1-1 1-2] [2-1 2-2] [3-1 3-2]]
}
值類型
數組本身是值類型的,所以當重新複製一份數組時,不會受到前數組的影響。
這相當於深拷貝。
如下示例,單一的數組中存入值類型,互不影響。
package main
import (
"fmt"
)
func main() {
var arr = [...]int{1, 2, 3}
// 數組是值類型
newArr := arr
arr[0] = 10
fmt.Println(arr) // [10 2 3]
fmt.Println(newArr) // [1 2 3]
}
示例二,二維數組的改變,也不會引發另一個數組的改變,故此說是深拷貝。
package main
import (
"fmt"
)
func main() {
var arr = [...][1]int{
{10},
}
// 數組是值類型
newArr := arr
arr[0][0] = 100
fmt.Println(arr) // [[100]]
fmt.Println(newArr) // [[10]]
}
循環遍歷
可以使用for
的索引循環,也可以使用range
來進行數組遍歷。
package main
import (
"fmt"
)
func main() {
var arr = [10]string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"}
// 索引循環
for index := 0; index < len(arr); index++ {
fmt.Println(index)
}
// range循環
for index,ele := range arr{
fmt.Println(index)
fmt.Println(ele)
}
}
切片相關
切片是基於數組的一層封裝,非常靈活且支持擴容。
你可以將它當作一個更加高級的數組。
注意:切片是引用類型,不能直接做比較,只能和nil做比較。
nil則相當於null
切片聲明
聲明切片類型的基本語法如下:
var 變量名 []切片類型
以下是一些示例,對於單純的創建切片來說,它沒有像數組一樣的容量限制,但是具有類型限制:
package main
import (
"fmt"
)
func main() {
var a []string
var b = []int{}
var c = []bool{true,false}
fmt.Println(a) // []
fmt.Println(b) // []
fmt.Println(c) // [true false]
fmt.Println(a == nil) // true
}
切片引用
如果對一個數組進行切片取值,那麼切片的元素引用於原數組中的元素。
package main
import (
"fmt"
)
func main() {
var arr = [...]string{"①", "②", "③", "④", "⑤"}
var arrScile = arr[1:3] // 引用數組的②③
arr[1] = "二"
arr[2] = "三"
fmt.Println(arrScile) // [二 三] 跟隨發生改變
}
這裡是常見的一些切片引用的操作,需要注意的是切片引用顧頭不顧尾。
a[2:] // 等同於 a[2:len(a)]
a[:3] // 等同於 a[0:3]
a[:] // 等同於 a[0:len(a)]
長度容量
引用切片的len()
指的是切出來的元素數量。
而cap()
則是被切片的數組中,從第一個位置切的地方往後算還剩多少個元素。
長度就是當前切片或者數組中存在的元素個數
容量就是最多可以容納的元素個數
package main
import (
"fmt"
)
func main() {
var arr = [...]string{"①", "②", "③", "④", "⑤"}
var arrSlice = arr[1:3]
fmt.Println(len(arrSlice)) // 2
fmt.Println(cap(arrSlice)) // 4
}
如何理解?這裡有一幅圖,因為有指針的關係。所以才是引用:
make切片
上面的切片都是經過數組創建出來的,切片的容量不能由我們自己進行控制。
但是使用make()
的話就可以動態的創建出一個切片。
make([]類型, size切片中元素數量, cap切片容量)
如下示例:
package main
import (
"fmt"
)
func main() {
// 創建一個string類型的切片 默認存放3個元素 最大可存放5個
var slice = make([]string,3,5)
fmt.Println(slice) // [ ]
}
append
使用內置函數append()
,可以為切片添加元素。
可以添加一個元素,也可以添加多個元素,甚至可以利用解構語法進行切片合併。
需要注意的是,當使用append()
方法後,應該用一個變量來接收它。
以下是添加多元素的示例
package main
import (
"fmt"
)
func main() {
var slice []string
slice = append(slice, "一", "二")
fmt.Println(slice) // [一 二]
}
以下是合併兩個切片的示例
package main
import (
"fmt"
)
func main() {
var (
slice1 []string
slice2 []string
)
slice1 = append(slice1, "一", "二","三")
slice2 = append(slice2, "四", "五")
slice1 = append(slice1,slice2...)
fmt.Println(slice1) // [一 二 三 四 五]
}
copy
切片是引用類型,如果想將其作為值類型的傳遞可以用copy
。
它有兩個參數,分別是destSlice
和srcSlice
。
destSlice:目標切片
srcSlice:數據來源切片
注意:目標切片必須是make創建的切片,否則將會失敗
package main
import (
"fmt"
)
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 5, 5)
// 目標切片 數據來源切片
copy(slice2, slice1)
slice1[0] = 100
fmt.Println(slice1) // [100 2 3 4 5]
fmt.Println(slice2) // [1 2 3 4 5]
}
刪除元素
切片中沒有提供任何刪除元素的方法,但是可以利用append()
返回新切片的特性,來達到刪除元素的目的。
package main
import (
"fmt"
)
func main() {
slice1 := []int{1, 2, 3, 4, 5}
// 刪除3
slice1 = append(slice1[:2],slice1[3:]...)
fmt.Print(slice1) // [1 2 4 5]
}
擴容行為
切片不需要指定容量,它會自動進行擴容。如下示例:
func main() {
var a []string
fmt.Println(cap(a)) // 0 初始值
a = append(a,"1")
fmt.Println(cap(a)) // 1
a = append(a,"2")
fmt.Println(cap(a)) // 2
a = append(a,"3")
fmt.Println(cap(a)) // 4
a = append(a,"4")
fmt.Println(cap(a)) // 4
a = append(a,"5")
fmt.Println(cap(a)) // 8
a = append(a,"6")
fmt.Println(cap(a)) // 8
a = append(a,"7")
fmt.Println(cap(a)) // 8
a = append(a,"8")
fmt.Println(cap(a)) // 8
}
可以通過查看$GOROOT/src/runtime/slice.go
源碼,其中擴容相關代碼如下:
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
從上面的代碼可以看出以下內容:
- 首先判斷,如果新申請容量(cap)大於2倍的舊容量(old.cap),最終容量(newcap)就是新申請的容量(cap)。
- 否則判斷,如果舊切片的長度小於1024,則最終容量(newcap)就是舊容量(old.cap)的兩倍,即(newcap=doublecap),
- 否則判斷,如果舊切片長度大於等於1024,則最終容量(newcap)從舊容量(old.cap)開始循環增加原來的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最終容量(newcap)大於等於新申請的容量(cap),即(newcap >= cap)
- 如果最終容量(cap)計算值溢出,則最終容量(cap)就是新申請容量(cap)。
需要注意的是,切片擴容還會根據切片中元素的類型不同而做不同的處理,比如int
和string
類型的處理方式就不一樣。
關於上面的示例,其實只看前兩個判斷就夠了。因為舊的容量沒有超過1024
循環遍歷
可以使用for
的索引循環,也可以使用range
來進行切片遍歷。
package main
import (
"fmt"
)
func main() {
var arr = []string{"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"}
// 索引循環
for index := 0; index < len(arr); index++ {
fmt.Println(index)
}
// range循環
for index,ele := range arr{
fmt.Println(index)
fmt.Println(ele)
}
}