slice的append操做注意事項

從其餘語言例如C++,若是不能很好的理解slice和slice的append操做,把slice做爲引用類型傳參,會致使丟失數據。
首先,函數之間傳遞變量時老是以值的方式傳遞的。也就是說,即便你傳的slice之類的引用類型,也是傳的它的一個copy。
例子:app

package main

import "fmt"

type DbItem struct {
    Id  int16
    Cnt int32
}

func combineItem(itemList []DbItem, id int16, cnt int32) {
    item := DbItem{Id: int16(id), Cnt: int32(cnt)}
    itemList = append(itemList, item)
    fmt.Printf("combineItem itemList addr[%p]  cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
}

func main() {
    itemList := make([]DbItem, 1, 5)
    combineItem(itemList, int16(1), int32(2))
    fmt.Printf("1.cap沒有擴容的狀況 itemList addr[%p] cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
    itemList = make([]DbItem, 5, 5)
    combineItem(itemList, int16(1), int32(2))
    fmt.Printf("2.cap被擴容,新DbItem追加在slice最後 itemList addr[%p] cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
    itemList = make([]DbItem, 5, 5)
    combineItem(itemList[:1], int16(1), int32(2))
    fmt.Printf("3.cap沒被擴容,新DbItem追加在slice中間 itemList addr[%p] cap[%d] len[%d] values: %v \n", itemList, cap(itemList), len(itemList), itemList)
}

運行結果:
combineItem itemList addr[0xc4200480f0] cap[5] len[2] values: [{0 0} {1 2}]
1.cap沒有擴容的狀況 itemList addr[0xc4200480f0] cap[5] len[1] values: [{0 0}]
combineItem itemList addr[0xc42003e050] cap[10] len[6] values: [{0 0} {0 0} {0 0} {0 0} {0 0} {1 2}]
2.cap被擴容,新DbItem追加在slice最後 itemList addr[0xc420048120] cap[5] len[5] values: [{0 0} {0 0} {0 0} {0 0} {0 0}]
combineItem itemList addr[0xc420048150] cap[5] len[2] values: [{0 0} {1 2}]
3.cap沒被擴容,新DbItem追加在slice中間 itemList addr[0xc420048150] cap[5] len[5] values: [{0 0} {1 2} {0 0} {0 0} {0 0}] 函數

當slice_c=append(slice_a,slice_b)中len(slice_a)+len(slice_b)<=cap(slice_a)時不會擴容,不然會被擴爲cap(slice_a)的兩倍,若是slice len大於1024了,會擴容四分之一cap。具體實現源碼以下:ui

newcap := old.cap
if newcap+newcap < cap {
    newcap = cap
} else {
    for {
        if old.len < 1024 {
            newcap += newcap
        } else {
            newcap += newcap / 4
        }
        if newcap >= cap {
            break
        }
    }
}

從例2能夠看見,append返回的slice指向了個新的地址。可是即便沒擴容,slice指向同一個地址的狀況下,爲什麼slice_c仍是不等於slice_a呢?其實slice不是單純一個指針,而是個C的struct實現的:指針

struct    Slice
{    // must not move anything
    byte*    array;        // actual data
    uintgo    len;        // number of elements
    uintgo    cap;        // allocated number of elements
};

在$GOROOT/src/pkg/runtime/runtime.h能夠看見源碼。
在沒擴容的狀況下,傳入slice_a的數據改變是會影響函數外的slice_a的值的,由於他們的byte*指向同一個地址。
擴容的狀況下就沒有這個影響了。不管擴容與否,slice的值還取決於len,因此即便slice的byte*指向同一地址,打出來的結果仍是不同的。因此當函數裏面有append,千萬別把slice做爲引用類型的參數用了。code

相關文章
相關標籤/搜索