前言
在golang中,常見的序列型數據類型有array和slice這兩種,但array因爲其固定長度的限制,在實際使用中用得不多,slice則更爲常用。下面簡單介紹和對比一下這兩種相似卻又有很多差異的數據類型。
Array:
概念:
在golang中,數組由相同類型的元素組成的具有固定長度的一種序列型複合數據類型。
聲明和使用:
package main
import "fmt"
/*
數組: array在go裏是固定長度、每個元素類型必須相同的,因此使用較少,slice使用反而較多
*/
func main() {
var a [3]int // 3個整數的數組
fmt.Println(a) // [0 0 0]
fmt.Println(a[0])
for i, v := range a {
fmt.Printf("%d %d\n", i, v) // 打印索引和元素
}
for _, v2 := range a {
fmt.Printf("%d\n", v2) // 打印元素
}
// 默認情況下,數組創建後元素都是零值,可以使用數組字面量來初始化一個數組的元素
var q [3]int = [3]int{1, 2, 3}
fmt.Println(q) // [1 2 3]
var q2 [3]int = [3]int{1, 2}
fmt.Println(q2) // [1 2 0]
var q3 = [3]int{1, 2, 3}
fmt.Println(q3) // 縮寫用這個方式比較簡潔
/*
數組長度是數組類型的一部分,所以q4和q5是不同的類型.
數組的長度必須是常量表達式,因此數組長度在聲明時必須確定
*/
q4 := [...]int{1, 2, 3, 4}
q5 := [...]int{1, 2, 3, 4, 5}
fmt.Printf("Type: %T\n", q4) // Type: [4]int
fmt.Printf("Type: %T\n", q5) // Type: [5]int
//q4 = q5 // 編譯錯誤,賦值失敗,不同類型不能賦值
}
總結
在golang中array的長度是固定的,聲明時必須以數值形式明確指定或者以’…'形式間接指定元素長度,且不同的長度的array類型不同不能轉換,因此使用場景有限。
Slice
概念
在golang中,數組由相同類型的元素組成的可變長度的一種序列型複合數據類型。在理解上來說,slice和array唯一的區別是長度可變
聲明和使用
package main
import (
"bytes"
"fmt"
)
/*
slice數據類型在聲明時如果不指定len/cap,則slice的cap/len值默認以聲明時的數組的長度爲準。
len: 長度。slice包含的元素的個數,可變
cap: 容量。slice當前可接納的元素的個數,根據了len值動態變化
*/
func main() {
// 聲明方式1
var b []int // 聲明一個空slice,默認len和cap的值都爲0。聲明長度爲0的array: var b [0]int,這兩者聲明方式非常相似
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(b), cap(b), b) // Slice a , length: 0 cap: 0 content: []
// 聲明方式2
a := []int{1, 2, 3, 4, 5}
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(a), cap(a), a) // Slice a , length: 5 cap: 5 content: [1 2 3 4 5]
a = append(a, 6) // 追加元素
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(a), cap(a), a) // Slice a , length: 6 cap: 10 content: [1 2 3 4 5 6]
// 聲明方式3
c := make([]int, 5, 10) // make(type, len[, cap])
fmt.Printf("Slice a , length: %d cap: %d content: %v\n", len(c), cap(c), c) // Slice a , length: 5 cap: 10 content: [0 0 0 0 0]
// copy方法
d := []int{1, 2}
e := []int{3, 4, 5, 6}
copy(e, d) // copy(to,from) ,第一個參數是目標slice,第二個參數是源slice
fmt.Println(d, e) // [1 2] [1 2 5 6]
f := []int{3, 4, 5, 6}
copy(d, f) // copy(to,from) ,第一個參數是目標slice,第二個參數是源slice
fmt.Println(d, f) // [3 4] [3 4 5 6]
// slice的比較
g := []string{"a", "b"}
h := []string{"a", "b", "c"}
fmt.Println(g != nil) // true
//fmt.Println(g == h) // 這裏會編譯錯誤,slice沒有==比較方法
// 只能自定義方法來比較兩個slice,例如下方自定義的equal方法:
fmt.Println(equal(g, h))
// 檢查slice是否爲空
fmt.Println(len(g) != 0)
fmt.Println(g != nil) // 不推薦用這個方法,因爲即使g != nil,g也有可能是空值
/*
------------------------------------Slice增加/刪除/反轉元素/轉換格式等常用操作------------------------------------
*/
// 尾部操作
j := []int{1, 2, 3, 4, 5}
fmt.Println(append(j, 6)) // [1 2 3 4 5 6] 尾部插入
top := j[len(j)-1]
fmt.Println(top) // 5 獲取尾部(棧頂)元素
j = j[:len(j)-1] // 彈出尾部元素
fmt.Println(j) // [1 2 3 4]
// slice中間移除/插入操作,沒有自帶函數,需要自己寫方法
j = sliceRemove(j, 2)
fmt.Println(j) // [1 2 4]
j = sliceInsert(j, 2, 3)
fmt.Println(j) // [1 2 3 4]
// 反轉元素
j = sliceReverse(j)
fmt.Println(j) // [4 3 2 1]
// 轉換爲字符串格式
fmt.Println(intsToString([]int{1, 2, 3})) // [1 & 2 & 3]
}
func sliceRemove(slice []int, i int) []int {
// 移除slice的指定索引位置的元素
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
func sliceInsert(slice []int, i int, n int) []int {
// 將元素插入到slice的指定索引位置
sub := []int{n}
for _, e := range slice[i:] {
sub = append(sub, e)
}
copy(slice[i:], sub)
slice = append(slice, sub[len(sub)-1])
return slice
}
func sliceReverse(slice []int) []int {
// 反轉slice
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
slice[i], slice[j] = slice[j], slice[i]
}
return slice
}
func equal(x, y []string) bool {
if len(x) != len(y) {
return false
}
flag := true
for i, e := range x {
if e != y[i] {
flag = false
break
}
}
return flag
}
func intsToString(values []int) string {
var buf bytes.Buffer
buf.WriteByte('[')
for i, v := range values {
if i > 0 {
buf.WriteString(" & ")
}
//fmt.Println(v)
fmt.Fprintf(&buf, "%d", v) //輸出重定向
}
buf.WriteByte(']')
return buf.String()
}
Slice說明
1.聲明一個空slice,默認len和cap的值都爲0。聲明長度爲0的array的方式: var b [0]int,這兩者聲明方式非常相似
2.slice在新增元素的時候,如果len超過cap,會動態擴容cap,根據網上查到的擴容函數的源碼:
func growslice(et *_type, old slice, cap int) slice {
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小於len時啓動擴容,若cap < 1024,則申請新的內存cap翻倍,若大於2014,則cap提升1/4。(這與python的list擴容策略很相似)
3.使用make方式聲明可以指定cap以直接申請一塊連續的內存空間,避免頻繁地改變cap容量而帶來額外的開銷。
4.copy函數複製slice時,假設源slice長度爲x,目標slice長度爲y,則覆蓋策略爲:
如果 x<y,則用源slice的所有元素逐個覆蓋目標slice的前x個元素;
如果 x>y,則用源slice前y個元素逐個覆蓋目標slice的所有元素。