go是宣揚(yáng)實(shí)用主義的語(yǔ)言,很多時(shí)候都把c中的最佳實(shí)踐直接規(guī)定成語(yǔ)法了。其中之一就是slice,簡(jiǎn)單但是非常容易踩坑。
先看一個(gè)小例子:
func main() {
a := make([]int, 2, 2)
a[0], a[1] = 1, 2
b := append(a[0:1], 3)
c := append(a[1:2], 4)
fmt.Println(b,c)
}
在這個(gè)小例子中,原本是希望將a[0:1]作為b的前綴,然后追加上3;將a[1:2]作為c的前綴,然后追加上4。但實(shí)際上輸出結(jié)果并不是原本期望的[1 3] [2 4],而變成了[1 3] [3 4]。這是為什么呢?
我們知道數(shù)據(jù)結(jié)構(gòu)中數(shù)組是非常高效的,可以直接尋址,但是有個(gè)缺陷,難以擴(kuò)容。所以slice被設(shè)計(jì)為指向數(shù)組的指針,在需要擴(kuò)容時(shí),會(huì)將底層數(shù)組上的值復(fù)制到一個(gè)更大的數(shù)組上然后指向這個(gè)新數(shù)組。
slice有個(gè)特性是允許多個(gè)slice指向同一個(gè)底層數(shù)組,這是一個(gè)有用的特性,在很多場(chǎng)景下都能通過(guò)這個(gè)特性實(shí)現(xiàn) no copy 而提高效率。但共享同時(shí)意味著不安全。b在追加3時(shí)實(shí)際上覆蓋了a[1],導(dǎo)致c變成了[3 4]。
怎么解決呢?防止共享數(shù)據(jù)的出現(xiàn)問(wèn)題需要注意兩條,只讀和復(fù)制,或者統(tǒng)一歸納為不可變。
寫(xiě)法1,make出一個(gè)新slice,然后先copy前綴到新數(shù)組上再追加:
func main() {
a := make([]int, 2, 2)
a[0], a[1] = 1, 2
b := make([]int, 1)
copy(b, a[0:1])
b = append(b, 3)
c := make([]int, 1)
copy(c, a[1:2])
c = append(c, 4)
fmt.Println(b, c)
}
寫(xiě)法2,利用go中slice的一個(gè)小眾語(yǔ)法,a[0:1:1] (源[起始index,終止index,cap終止index]),強(qiáng)迫追加時(shí)復(fù)制到新數(shù)組。
func main() {
a := make([]int, 2, 2)
a[0], a[1] = 1, 2
b := append(a[0:1:1], 3)
c := append(a[1:2:2], 4)
fmt.Println(b, c)
}