Go slice切片的“陷阱”和本質

HideOnBush發表於2019-05-10

文章說明

總結了go語言中切片slice的特殊性和使用時的注意事項。

個人理解,不足之處歡迎指出。

slice:切片,是go語言中一種常用的資料結構,基於陣列構建,表示相同資料型別的集合。

陣列

Go中陣列型別表示固定長度的相同型別的資料的集合,資料在記憶體中連續儲存,可以通過下標索引,但是又有特殊的地方:

  • 陣列是值型別,一個陣列變數表示整個陣列,而不是指向陣列的首元素的指標,這和C語言不同。
  • 將陣列賦值給另一個陣列,或者陣列作函式引數傳遞時,會將陣列的全部資料拷貝一份過去而不是傳遞一個指標。
  • 陣列型別包括長度,即[5]int和[10]不是一種型別。

所以Go語言中使用陣列傳遞資料效率很低,通常使用切片。

切片

切片是一個陣列片段的描述,包含了指向陣列片段的指標,片段的長度len和容量cap(陣列片段的最大長度),但是切片本身並不是真正的指標型別

切片的特性

  1. 可以自動擴容 使用append()向切片追加資料,資料是被新增到切片指向的片段末尾,長度等於容量時切片就會自動擴容,擴容的細節後面的文章再討論。
  2. 切片之間賦值或者切片作函式引數傳遞時,是將指向陣列片段的指標傳遞過去,所以改變一個會影響另一個。

切片的陷阱

切片作函式引數傳遞或淺拷貝時,之所以改變一個切片的資料會影響另一個切片,是因為兩個切片中中包含了指向同一陣列片段的指標。

一切看似正常?但是當一個切片發生擴容時,會將當前切片內的資料複製到另一片記憶體區域,該切片的陣列片段的地址發生改變,所以當切片擴容時修改一個切片的資料時不會再影響到另一個切片!此時只能通過傳遞切片本身的地址來解決。

擴容時出錯的程式碼如下:

package main

import "fmt"

func testSlice(slice []int) {
slice = append(slice, 6, 7, 8, 9, 10)
fmt.Println("testSlice:",slice)
}
func main() {
slice := []int{1, 2, 3, 4, 5}

	testSlice(slice)
fmt.Println("main:",slice)
}
複製程式碼

切片的本質

所以,切片不是指標型別,切片資料型別是包含指向一個陣列片段的指標,和當前陣列片段的長度,以及當前陣列最大容量的一種複合資料結構

相關文章