陣列基礎#
陣列是固定長度的同型別元素序列,長度是型別的一部分。
// 陣列宣告
var arr1 [5]int // 零值初始化
arr2 := [5]int{1, 2, 3, 4, 5} // 完整初始化
arr3 := [...]int{1, 2, 3} // 自動推斷長度
arr4 := [5]int{0: 1, 4: 5} // 指定索引初始化
[3]int和[5]int是不同的型別。陣列長度是編譯時期常數,無法在執行時期改變。
切片本質#
切片是對底層陣列的一個「視窗」,包含三個欄位:
type slice struct {
array unsafe.Pointer // 指向底層陣列的指標
len int // 切片長度
cap int // 切片容量
}// 切片建立方式
s1 := make([]int, 5) // 長度 5,容量 5
s2 := make([]int, 5, 10) // 長度 5,容量 10
s3 := []int{1, 2, 3} // 字面量初始化
s4 := arr[1:4] // 從陣列切取切片視窗比喻#
把切片想像成一個可以移動、伸縮的視窗,視窗後面是底層陣列:
arr := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
s := arr[2:6] // s = [2, 3, 4, 5]
// 底層陣列:[0, 1, 2, 3, 4, 5, 6, 7]
// ↑──視窗──↑
// len(s) = 4(視窗寬度)
// cap(s) = 6(從起點到陣列末端的距離)切片只能向右擴展(增加容量),無法向左擴展。一旦起點確定,容量上限就固定了。
切片擴容機制#
s := make([]int, 0, 4)
s = append(s, 1, 2, 3, 4) // 長度 4,容量 4
s = append(s, 5) // 觸發擴容
// 擴容規則(Go 1.18+):
// - 容量 < 256:新容量 = 舊容量 * 2
// - 容量 >= 256:新容量 = 舊容量 * 1.25 + 192(約略)擴容會建立新的底層陣列並複製元素:
- 擴容操作有效能開銷
- 擴容後切片不再與原底層陣列共用記憶體
切片共用底層陣列的陷阱
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[1:4] // [2, 3, 4]
s2 := arr[2:5] // [3, 4, 5]
// s1 和 s2 共用同一個底層陣列
s1[1] = 100 // 修改 s1
fmt.Println(s2) // [100, 4, 5] - s2 也被影響!
// 解法:用 copy 建立獨立切片
s3 := make([]int, len(s1))
copy(s3, s1)