一、數組
1.1 數組賦值給數組
Go數組是值類型,因此賦值操作和函數傳參數會複製整個數組的數據,例:
func main() {
a := [3]int{1, 2, 3}
b := a
fmt.Printf("a addr: %p, a[0] addr: %p\n", &a, &(a[0]))
fmt.Printf("b addr: %p, b[0] addr: %p\n", &b, &(b[0]))
test(a)
}
func test(arr [3]int) {
fmt.Printf("arr addr: %p, arr[0] addr: %p\n", &arr, &(arr[0]))
}
結果:
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0
b addr: 0xc04204a100, b[0] addr: 0xc04204a100
可以看到,b的地址和a的地址不同,同時可以看到,數組的地址即爲數組第一個元素的地址。
1.2 數組賦值給數組指針
上面已經看到數組直接賦值是值傳遞,可以考慮用指針來實現傳地址,例:
func main() {
a := [3]int{1, 2, 3}
b := &a
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, value: %p, b[0] addr: %p, value: %v\n", &b, b, &(b[0]), b)
}
結果:
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0, value: [1 2 3]
b addr: 0xc04206a018, value: 0xc04204a0e0, b[0] addr: 0xc04204a0e0, value: &[1 2 3]
可以看到,指針的地址和數組的地址不一樣,指針的值是數組的地址,即這裏指針b是一個指向數組a地址的變量。這樣無論是直接賦值給指針,還是在函數中用指針來傳遞,都可以達到不復制數組,並且修改原數組值的目的。
二、切片
切片運行時實際結構爲SliceHeader ,其結構定義爲:
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
三個成員即切片指向的數據源數組地址,切片長度和切片容量。當切片之間傳遞時,實際上是SliceHeader 之間的值傳遞,由於傳遞之後,Data指向的數組地址是同一個,所以修改操作會同步。下面從幾個實際例子來探究不同情況下的運行結果。
1.2 從數組得到切片
1、切片得到數組時,如果切片沒有進行擴容,則指向的數據源還是此數組,任何對數組或切片的值修改操作,另一個的值也隨之改變
切片未擴容
Go 切片是可以從數組得到的,以下代碼,從切片得到數組:
func main() {
a := [3]int{1, 2, 3}
b := a[:]
fmt.Printf("a addr: %p, a[0] addr: %p\n", &a, &(a[0]))
fmt.Printf("b addr: %p, b[0] addr: %p\n", &b, &(b[0]))
}
結果:
a addr: 0xc04200c4a0, a[0] addr: 0xc04200c4a0
b addr: 0xc0420023e0, b[0] addr: 0xc04200c4a0
可以看到,切片b指向了新地址,但是第一個元素的地址和數組a的一致。那這裏修改數組某個元素值切片的值會改變麼,或者修改切片的某個元素值數組的值會改變麼?下面繼續測試:
func main() {
a := [3]int{1, 2, 3}
b := a[:]
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b
a[0] = 10
b[1] = 20
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
}
結果:
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0, value: [1 2 3]
b addr: 0xc0420463a0, b[0] addr: 0xc04204a0e0, value: [1 2 3]E
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0, value: [10 20 3]
b addr: 0xc0420463a0, b[0] addr: 0xc04204a0e0, value: [10 20 3]
可以看到,不管是修改數組的值,還是切片的值,另一個對應的值也改變了,這驗證了切片指向的數據源是數組a。
切片擴容情況
下面是從數組得到切片後,執行append操作:
func main() {
a := [3]int{1, 2, 3}
b := a[:]
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
b = append(b, 4)
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
a[0] = 10
b[1] = 20
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
}
結果:
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0, value: [1 2 3]
b addr: 0xc0420463a0, b[0] addr: 0xc04204a0e0, value: [1 2 3]
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0, value: [1 2 3]
b addr: 0xc0420463a0, b[0] addr: 0xc042068030, value: [1 2 3 4]
a addr: 0xc04204a0e0, a[0] addr: 0xc04204a0e0, value: [10 2 3]
b addr: 0xc0420463a0, b[0] addr: 0xc042068030, value: [1 20 3 4]
可以看到,擴容後切片b第一個元素的地址發生了變化,因此後續對數組值得修改和對切片值的修改,都不會影響到另一個的值。
切片之間賦值
Golang中切片是引用類型,直接賦值後,修改任意一個的某個元素值,另一個也會隨之改變。但是如果在賦值之後,切片進行了擴容操作,則會指向新的數據源,因此修改值操作不會影響原來的切片。如果不想影響另一個的結果,可以用copy函數來實現。例:
func main() {
a := []int{1, 2, 3}
b := a
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b) //b[0]地址和a[0]地址一樣
fmt.Println("info a:", len(a), cap(a))
fmt.Println("info b:", len(b), cap(b))
b = append(b, 4)
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b) //b[0]地址變化了
fmt.Println("info a:", len(a), cap(a))
fmt.Println("info b:", len(b), cap(b))
a[0] = 10
b[1] = 20
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
}
結果:
a addr: 0xc0420463a0, a[0] addr: 0xc04204a0e0, value: [1 2 3]
b addr: 0xc0420463c0, b[0] addr: 0xc04204a0e0, value: [1 2 3]
info a: 3 3
info b: 3 3
a addr: 0xc0420463a0, a[0] addr: 0xc04204a0e0, value: [1 2 3]
b addr: 0xc0420463c0, b[0] addr: 0xc042068030, value: [1 2 3 4]
info a: 3 3
info b: 4 6
a addr: 0xc0420463a0, a[0] addr: 0xc04204a0e0, value: [10 2 3]
b addr: 0xc0420463c0, b[0] addr: 0xc042068030, value: [1 20 3 4]
切片append分析
當容量足夠時,直接在當前長度的下一個位置保存數據,即使還有其他切片也指向這一塊地址。例:
func main() {
data := [5]int{1, 2, 3, 4, 5}
a := data[0:1]
b := data[0:4]
fmt.Println("info a:", len(a), cap(a))
fmt.Println("info b:", len(b), cap(b)) //切片a和b數據源都指向data,但是長度不同
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
a = append(a, 10) //向a添加數據,直接在數據第二個位置保存數據,這樣data結果也改變了
fmt.Println("info a:", len(a), cap(a))
fmt.Println("info b:", len(b), cap(b))
fmt.Printf("a addr: %p, a[0] addr: %p, value: %v\n", &a, &(a[0]), a)
fmt.Printf("b addr: %p, b[0] addr: %p, value: %v\n", &b, &(b[0]), b)
}
結果:
info a: 1 5
info b: 4 5
a addr: 0xc0420463a0, a[0] addr: 0xc042068030, value: [1]
b addr: 0xc0420463c0, b[0] addr: 0xc042068030, value: [1 2 3 4]
info a: 2 5
info b: 4 5
a addr: 0xc0420463a0, a[0] addr: 0xc042068030, value: [1 10]
b addr: 0xc0420463c0, b[0] addr: 0xc042068030, value: [1 10 3 4]
可以看到,由於多個切片指向同一塊數據源,任何修改操作都會使得其他變量結果改變。
總結
對於切片,只要清楚它指向的數據源是否有改變,即關注append前後時,容量是否有變化,就能判斷其運行結果。