看完這期圖解,別再搞不清切片拷貝了
本文轉(zhuǎn)載自微信公眾號「Golang技術(shù)分享」,作者機器鈴砍菜刀 。轉(zhuǎn)載本文請聯(lián)系Golang技術(shù)分享公眾號。
在剛使用 Go 時,菜刀曾將 Python 深拷貝手法[:]用于 Go 中 ,結(jié)果造成了 bug。相信不少轉(zhuǎn)語言的 Gopher 也在切片拷貝上栽過跟頭。
切片是 Go 中最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),之前我們談過切片傳遞、切換轉(zhuǎn)換、切片擴容等內(nèi)容。
本文,我們將探討切片拷貝,就切片的三種拷貝方式進行圖解分析,希望幫助讀者鞏固一下基礎(chǔ)知識。
深淺拷貝
所謂深淺拷貝,其實都是進行復(fù)制,主要區(qū)別在于復(fù)制出來的新對象和原來的對象,它們的數(shù)據(jù)發(fā)生改變時,是否會相互影響。
簡單而言,B 復(fù)制 A,如果 A 的數(shù)據(jù)發(fā)生變化,B 也跟著變化,這是淺拷貝。反之, 如果 B 不發(fā)生變化,則為深拷貝。
深淺拷貝差異的根本原因在于,復(fù)制出來的對象與原對象是否會指向同一個地址。
以下是 Python 中 list 與 Go 中 slice 深淺拷貝的表現(xiàn)差異
- // Python 版
- if __name__ == '__main__':
- a = [1, 2, 3]
- b = a
- c = a[:]
- a[0] = 100
- print(a, b, c) // [100, 2, 3] [100, 2, 3] [1, 2, 3]
- // Golang 版
- func main() {
- a := []int{1, 2, 3}
- b := a
- c := a[:]
- a[0] = 100
- fmt.Println(a, b, c) // [100 2 3] [100 2 3] [100 2 3]
- }
發(fā)現(xiàn)沒有?在 Go 中 [:] 操作并不是深拷貝。
= 拷貝
通過=操作符拷貝切片,這是淺拷貝。
- func main() {
- a := []int{1, 2, 3}
- b := a
- fmt.Println(unsafe.Pointer(&a)) // 0xc00000c030
- fmt.Println(a, &a[0]) // [100 2 3] 0xc00001a078
- fmt.Println(unsafe.Pointer(&b)) // 0xc00000c048
- fmt.Println(b, &b[0]) // [100 2 3] 0xc00001a078
- }
圖解
[:] 拷貝
通過[:]方式復(fù)制切片,同樣是淺拷貝。
- func main() {
- a := []int{1, 2, 3}
- b := a[:]
- fmt.Println(unsafe.Pointer(&a)) // 0xc0000a4018
- fmt.Println(a, &a[0]) // [1 2 3] 0xc0000b4000
- fmt.Println(unsafe.Pointer(&b)) // 0xc0000a4030
- fmt.Println(b, &b[0]) // [1 2 3] 0xc0000b4000
- }
圖解
我們有時會使用[start: end]進行拷貝。例如,b:=a[1:],那它的拷貝情況如何
copy() 拷貝
上述兩種方式都是淺拷貝,如果要切片深拷貝,需要用到copy()內(nèi)置函數(shù)。
copy()函數(shù)簽名如下
- func copy(dst, src []Type) int
其返回值代表切片中被拷貝的元素個數(shù)
- func main() {
- a := []int{1, 2, 3}
- b := make([]int, len(a), len(a))
- copy(b, a)
- fmt.Println(unsafe.Pointer(&a)) // 0xc00000c030
- fmt.Println(a, &a[0]) // [1 2 3] 0xc00001a078
- fmt.Println(unsafe.Pointer(&b)) // 0xc00000c048
- fmt.Println(b, &b[0]) // [1 2 3] 0xc00001a090
- }
圖解
copy 的元素數(shù)量與原始切片和目標(biāo)切片的大小、容量有關(guān)系
- func main() {
- a := []int{1, 2, 3}
- b := []int{-1, -2, -3, -4}
- copy(b, a)
- fmt.Println(unsafe.Pointer(&a)) // 0xc0000a4018
- fmt.Println(a, &a[0]) // [1 2 3] 0xc0000b4000
- fmt.Println(unsafe.Pointer(&b)) // 0xc0000a4030
- fmt.Println(b, &b[0]) // [1 2 3 -4] 0xc0000aa060
- }
圖解
總結(jié)
切片是 Go 語言中最基本的數(shù)據(jù)結(jié)構(gòu),它的擴容與拷貝細節(jié),在理解不當(dāng)時,是很容易寫出程序 bug 的。
本文分別就切片的三種拷貝方式,=、[:]、copy()進行了探討。其中,=、[:]是淺拷貝,copy()拷貝是深拷貝。
這樣的圖解方式,你喜歡嗎~