hi, 大家好,我是 hhf。
有這麼一個 Go 面試題:請說出 slice 和 array 的區別?
這簡直就是送分題。現在思考一下,你咋樣回答才能讓面試官滿意呢?
我這裡就不貼這道題的答案了。但是我想記憶體方面簡單分析下 slice 和 array 的區別。
Array
func main() {
as := [4]int{10, 5, 8, 7}
fmt.Println("as[0]:", as[0])
fmt.Println("as[1]:", as[1])
fmt.Println("as[2]:", as[2])
fmt.Println("as[3]:", as[3])
}
這段很簡單的程式碼,宣告瞭一個 array。當然輸出結果也足夠簡單。
我們現在玩點花活,如何通過非正常的手段訪問陣列裡面的元素呢?在做這個事情之前是需要先知道 array 的底層結構的。其實很簡單,Go array 就是一塊連續的記憶體空間。如下圖所示
寫一段簡單的程式碼,我們不通過下標訪問的方式去獲取元素。通過移動指標的方式去獲取對應位置的指標。
func main() {
as := [4]int{10, 5, 8, 7}
p1 := *(*int)(unsafe.Pointer(&as))
fmt.Println("as[0]:", p1)
p2 := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + unsafe.Sizeof(as[0])))
fmt.Println("as[1]:", p2)
p3 := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + unsafe.Sizeof(as[0])*2))
fmt.Println("as[2]:", p3)
p4 := *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + unsafe.Sizeof(as[0])*3))
fmt.Println("as[3]:", p4)
}
結果:
as[0]: 10
as[1]: 5
as[2]: 8
as[3]: 7
下圖演示下獲取對應位置的值的過程:
Slice
同樣對於 slice 這段簡單的程式碼:
func main() {
as := []int{10, 5, 8, 7}
fmt.Println("as[0]:", as[0])
fmt.Println("as[1]:", as[1])
fmt.Println("as[2]:", as[2])
fmt.Println("as[3]:", as[3])
}
想要通過移動指標的方式獲取 slice 對應位置的值,仍然需要知道 slice 的底層結構。如圖:
func main() {
as := []int{10, 5, 8, 7}
p := *(*unsafe.Pointer)(unsafe.Pointer(&as))
fmt.Println("as[0]:", *(*int)(unsafe.Pointer(uintptr(p))))
fmt.Println("as[1]:", *(*int)(unsafe.Pointer(uintptr(p) + unsafe.Sizeof(&as[0]))))
fmt.Println("as[2]:", *(*int)(unsafe.Pointer(uintptr(p) + unsafe.Sizeof(&as[0])*2)))
fmt.Println("as[3]:", *(*int)(unsafe.Pointer(uintptr(p) + unsafe.Sizeof(&as[0])*3)))
var Len = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + uintptr(8)))
fmt.Println("len", Len)
var Cap = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&as)) + uintptr(16)))
fmt.Println("cap", Cap)
}
結果:
as[0]: 10
as[1]: 5
as[2]: 8
as[3]: 7
len 4
cap 4
用指標取 slice 的底層 Data 裡面的元素跟 array 稍微有點不同:
- 對 slice 變數 as 取地址後,拿到的是 SiceHeader 的地址,對這個指標進行移動,得到是 slice 的 Data, Len, Cap。
- 所以當拿到 Data 的值時,我們拿到的是 Data 所指向的 array 的首地址的值。
- 由於這個值是個指標,需要對這個值 *Data, 取到 array 真正的首地址的指標值
- 然後對這個值 &(*Data),獲取到真正的首地址,然後對這個值進行指標的移動,才能獲取到 slice 的陣列裡的值
獲取 slice cap 和 len:
獲取 slice 的 Data: