Chapter 3.1 复合类型-Arrays,Slices

发布时间 2023-11-21 10:28:34作者: 我听不见

数组 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`

var x2 [][]int // 二维切片
x1[0] = 12 // 使用方括号访问和设置值,另外 go 不支持 python 的负数 offset
var x3 []int // zero value is `nil`

// build-in functions
// go 当中为数不多的内建函数 len append
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]

nil 表示缺乏值的某种 Type
Capacity 是一个可能会大于数组 len 的,表示切片的内存层的容量上限,也是 Slices 灵活性的一个设计,每一次在 append() 值到 Slices 中的时候,都会进行自动扩容判断,扩容方法通过分配整片的内存空间(整块重新分配后把值重新复制过去,旧的内存空间会被垃圾收集器自动回收处理)

Go 的运行时 runtime 编译到了每一个 build 出来的二进制可执行文件内,不同于依赖虚拟机的语言
因为这个特质使得分发 Go 程序变得更容易,你不用让你的用户去装虚拟机了
自动扩容技术是通过 Go 的运行时 runtime 实现的,Go runtime 还包含其他一些服务:内存分配、垃圾回收、并发支持、网络和内建类型与函数

cap

cap(x1)

Slices 有自增容量的特质,如果你能知道 append 的大致范围,初始化的时候给一个接近的容量的值可以避免一定数量下重复的内存分配与回收

make

x := make([]int, 5) // [0 0 0 0 0]
x := make([]int, 0, 10) // len:0 cap:10

三种声明 Slices 的方式

// 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)

使用哪一种所依据的基本原则是:

尽可能减少重复的内存分配与回收次数

对 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]