复习 Golang Chapter 3.1 复合类型-Arrays,Slices
数组 Arrays
数组在 Go 中很少被直接使用,因为数组的长度被作为类型的一部分被使用 [3]int
[5]int
是不同的类型
这个数组和 C 语言的数组很不一样,C 的数组变量就是指向数组的指针,但是 offset 是 0
你不能使用一个变量代表数组的长度,类型不是在运行时确定的,它必须在编译时确定,这限制了数组在代码中的灵活性
不能使用类型转换在不同 size 的数组之间互相转换
不能写一个 generic 函数处理所有 size 的数组
不能分配不同 size 的数组到同一个变量
通常不使用数组,除非数组的长度是确定的
数组主要作为切片的底层实现,顺便暴漏给程序员偶尔使用
var x [3]int
var x = [3]int{1,2,3}
var x = [12]int{1,5:4,6,7:8} // sparse array [1 0 0 0 0 4 6 8 0 0 0 0]
var x = [...]int{1,3,6} // [1 3 6]
var x [2][3]int // [[0 0 0] [0 0 0]]
x[0] // read
x[0] = 10 // write
len(x) // length
切片 Slices
在 Go 中最常用的用于处理序列 sequence 的数据结构
切片不像数组,它不强制长度作为类型的一部分,所以可以写出处理任意长度序列的泛型函数 generic function
与数组声明方式的区别
var a1 = [...]int{1,2,3,4,5} // [1 2 3 4 5] 数组
var a2 = []int{1,2,3,4,5} // [1 2 3 4 5] 切片
自动 pdding zero value 的稀疏切片语法
var x1 = []int{1, 5:4, 6, 7:8} // [1 0 0 0 0 4 6 8] 指定了跳跃的 index,会自动 padding `zero value`
访问 getter 与设值 setter
x1[0] = 12 // 使用方括号访问和设置值,另外 go 不支持 python 的负数 offset
多维切片
var x2 [][]int // 二维切片
默认值
var x3 []int // []
辅助切片的内建函数 len, append, cap, make
len(x1) // 8
x1 = append(x1, 20) // [1 0 0 0 0 4 6 8 20]
append(x1, 20, 3, 4, 5) // [1 0 0 0 0 4 6 8 20 3 4 5]
append_test_x := []int{1,2,3}
append_test_y := []int{4,5,6}
append_test_x = append(append_test_x, append_test_y...) // [1 2 3 4 5 6]
Capacity
是一个可能会大于数组 len
的,表示切片的内存级别的容量上限。这体现了 Slices 灵活性与性能斟酌的一个设计(没有使用链表结构),每一次在 append()
值到 Slices 中的时候,都会进行自动扩容判断,扩容方法通过分配整片的内存空间(整块重新分配后把值重新复制过去,旧的内存空间会被垃圾收集器自动标记,等待回收处理)
cap(x1)
Go 的运行时 runtime 编译到了每一个 build 出来的二进制可执行文件内,不同于依赖虚拟机的语言
因为这个特质使得分发 Go 程序变得更容易,你不用让你的用户去装虚拟机(像 jvm 那样)了
自动扩容技术是通过 Go 的运行时 runtime 实现的,Go runtime 还包含其他一些服务:内存分配、垃圾回收、并发支持、网络和内建类型与函数
Slices 有自增容量的特质,如果你能知道 append 的大致范围,初始化的时候给一个尽可能接近实际容量的值可以避免一定数量的频繁内存分配与回收
x := make([]int, 5) // [0 0 0 0 0]
x := make([]int, 0, 1000) // 提前分配足够的值,避免分配回收开销 len:0 cap:1000
三种声明 Slices 的方式,空值声明,字面量显性声明,make 预声明
// zero value
var x []int // nil
var x1 = []int{} // not nil but length is 0
// init value
var y []int{1,2,3} // iterals
// for custom cap
z := make([]int, 0, 100)
使用哪一种所依据的基本原则是:
尽可能减少重复的内存分配与回收次数
在需要可能为空的值的时候,不使用 :=
,而是使用 var
// 声明的空值不同导致的差异
var x []int // // nil 用于 json 会转换为 false
var y = []int{} // zero-length 切片用于 json 会转换为空数组
fmt.Println(x, y) // [] []
fmt.Println(x == nil, y == nil) // true false
对 Slices 进行再切片处理
变量名[begin:end]
begin 取值到 end-1
x := []int{1,2,3,4,5}
y := x[:2] // [1 2]
z := x[1:] // [2 3 4 5]
d := x[1:3] // [2 3]
e := x[:] // [1 2 3 4 5]
注意,再切片只是引用赋值,不是拷贝赋值
append 与引用赋值的 overlapping 效果
x := []int{1, 2, 3, 4} // [1 2 3 4]
y := x[:2] // [1 2]
fmt.Println(cap(x), cap(y))
y = append(y, 30)
fmt.Println("x:", x) // [1 2 3 4]
fmt.Println("y:", y) // [1 2 30]
不要对再切片进行 append 操作
或者
使用全切片表达式 full slice expression 确保 append 不会引起覆盖操作
全切片表达式 full slice expression
y := x[:2:2]
z := x[2:4:4]