1. 程式人生 > 實用技巧 >go 切片和陣列

go 切片和陣列

一、區別

  陣列的長度是固定的,初始化後就不能修改長度,大家平時程式碼中比較少用。

  slice是對陣列的一個封裝,可以動態擴容,slice是一個結構體,包含三個欄位:底層陣列、長度、容量

二、初始化方式

  陣列

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

  切片

var a []int //當前值為nil,未分配記憶體空間
var b = []int{1, 2, 3}
var c = make([]int, 5) //初始化5個元素,預設值為0,容量為5,在後面追加元素就會觸發擴容
var d = make([]int, 0, 5
) //初始化0個元素,容量為5

注意:slice在追加元素時如果容量不夠會觸發擴容,原slice拷貝到新slice中,擴容規則網上的說法:原slice小於1024,新的slice容量變成原slice的2倍,大於1024時,新slice容量變成原slice的1.25倍。這裡的說法只是一個大概不是很準確,感興趣的朋友可以檢視網路上相關文章或原始碼,其實這對於我們平時開發影響不是很大,想要最求效能可以在初始化時預估容量,減少擴容和拷貝的次數。

三、拷貝

  第一種

var a = []int{1,2,3,4,5}
var b = a[2:4]  //拷貝a中部分資料到b中

b[0] = 12 //改變b第一個元素的值
log.Println(a,b) //輸出:[1 2 12 4 5] [12 4] 兩個陣列的值都被改變了,說明資料是地址拷貝 b = append(b,6,7,8,9) //追加元素,容量不夠引發擴容 b[0] = 13 //改變b第一個元素的值 log.Println(a,b) //輸出:[1 2 12 4 5] [13 4 6 7 8 9] b中的資料改變了,a中的值並沒有改變,擴容以後b和a中的元素已經沒有了關係

  第二種

var a = []int{1,2,3,4,5}
var b = make([]int,5)

copy(b,a) //值拷貝
b[0] = 12
log.Println(a,b) 
//輸出:[1 2 3 4 5] [12 2 3 4 5]

四、平時使用需要注意的地方

  1、作為函式引數傳遞(go語言的函式引數傳遞,只有值傳遞,沒有引用傳遞),但有時候我們會產生一些誤區,看看下面這個例子

func f(a []int){
    a[0] = 12
    a = append(a,6)
}

func main(){
    var a = []int{1,2,3,4,5}
    f(a)
    log.Println(a) //輸出:[12 2 3 4 5] 第一個元素改變了,但值6並沒有追加到a中
}

  2、range 中改變值

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

for _, v := range a { //不會改變a中元素的值(v只是一個臨時變數)
    v = v + 2
}
log.Println(a) //輸出:[1 2 3 4 5]

for i := range a { //會改變a中的值
    a[i] = i + 2
}
log.Println(a) //輸出:[2 3 4 5 6]

五、擴充套件

  1、slice的結構

func main(){
    var a = []int{1, 2, 3, 4, 5}
    s := (*SliceHeader)(unsafe.Pointer(&a))

    log.Println(s.Len,s.Cap) //輸出:5 5
}

type SliceHeader struct {
    Data uintptr    //底層陣列地址
    Len  int        //長度
    Cap  int        //容量
}

  2、append方法必須要有接收者

var a = []int{1, 2, 3, 4, 5}
append(a,6) //編譯失敗

  3、陣列的元素在記憶體中地址是連續的

var a = []int{1, 2, 3, 4, 5}
s := (*reflect.SliceHeader)(unsafe.Pointer(&a))

//根據陣列索引獲取元素值
arrGet := func(n int)int{
    if n >= s.Len{
        panic("索引越界")
    }
    v := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(s.Data))+unsafe.Sizeof(int(0))*uintptr(n)))
    return *v
}
log.Println(arrGet(0)) //輸出:1
log.Println(arrGet(1)) //輸出:2
log.Println(arrGet(5)) //輸出:panic: 索引越界

以上內容為個人理解,如有問題歡迎指出。