📌Golang📌基础📌G-数组和切片.txt
数组是同一种数据类型的固定长度的序列。
数组定义:var 数组名 [长度]数据类型,
数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
长度是数组类型的一部分,因此,[5]int和[10]int是不同的类型。
数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是len-1
如果下标在数组合法范围之外,则触发访问越界,会panic
数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
指针数组[n]*T,数组指针*[n]T。
数组的定义和初始化
var arr0 [2]bool // [false false]
var arr1 = [5]int{1, 2, 3} // [1 2 3 0 0]
var arr2 = [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
var arr3 = [5]string{2: "hello", 3: "world"} // [ hello world ]
值拷贝行为会造成性能问题,通常会建议使用slice,或数组指针。
内置函数len和cap都返回数组长度(元素数量)。
slice并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
切片的长度可以改变,因此,切片是一个可变的数组。
切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
cap可以求出slice最大扩张容量,不能超出数组限制。
0 <= len(slice) <= len(array),其中array是slice引用的数组。
切片的定义:var 变量名 []类型,比如
var word []string
var num []int
如果 slice == nil,那么len、cap结果都等于0。
切片初始化,可以通过引用自数组。(begin和end的值在[0,len]之间都不会越界触发panic)
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8] // [2 3 4 5 6 7]
var slice1 []int = arr[0:6] // 等价于 var slice = arr[:end],[0 1 2 3 4 5]
var slice2 []int = arr[5:10] // 等价于 var slice = arr[begin:],[5 6 7 8 9]
var slice3 []int = arr[0:10] // 等价于 var slice = arr[:],[0 1 2 3 4 5 6 7 8 9]
var slice4 = arr[:len(arr)-1] // 去掉切片的最后一个元素,[0 1 2 3 4 5 6 7 8]
可直接创建slice对象,自动分配底层数组。
s1 := []int{0, 1, 5: 100} // 通过初始化表达式构造,可使用索引号。
fmt.Println(s1, len(s1), cap(s1)) // [0 1 0 0 0 100] 6 6
通过make来创建切片
var slice0 = make([]int, 3) // [0 0 0] len和cap都是3
var slice1 = make([]int, 0, 3) // [] len为0,cap为3
读写操作实际目标是底层数组,只需注意索引号的差别。
data := [...]int{0, 1, 2, 3, 4, 5}
s := data[2:4]
s[0] += 100
s[1] += 200
fmt.Println(s) // [102 203]
fmt.Println(data) // [0 1 102 203 4 5]
使用make动态创建slice,避免了数组必须用常量做长度的麻烦。还可用指针直接访问底层数组,退化成普通数组操作。
s := []int{0, 1, 2, 3}
p := &s[2] // *int, 获取底层数组元素指针。
*p += 100
fmt.Println(s) // [0 1 102 3]
[][]T,是指元素类型为[]T
data := [][]int{
[]int{1, 2, 3},
[]int{100, 200},
[]int{11, 22, 33, 44},
}
fmt.Println(data) // [[1 2 3] [100 200] [11 22 33 44]]
可直接修改struct array/slice成员
d := [4]struct{ x int }{}
s := d[:]
d[1].x = 10
s[2].x = 20
fmt.Println(d) // [{0} {10} {20} {0}]
append向slice尾部添加数据,返回新的slice对象。
s1 := make([]int, 0, 5)
s2 := append(s1, 1)
fmt.Println(s1, s2) // [] [1]
超出原slice.cap限制,就会重新分配底层数组,即便原数组并未填满。
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
s = append(s, 100, 200) // 一次append两个值,超出s.cap限制。
fmt.Println(s, data) // 重新分配底层数组,与原数组无关。[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的len属性,改用索引号进行操作。
及时释放不再使用的slice对象,避免持有过期数组,造成GC无法回收。
Go中切片扩容的策略:
原始容量小于一定阈值,使用2倍扩容,超过一定阈值使用不同的扩容计算规则。
不同版本的扩容系数和规则有差异。不同的元素类型,阈值和计算规则也不相同。
注意:扩容扩大的容量都是针对原来的容量而言的,而不是针对原来数组的长度而言的。
函数copy在两个slice间复制数据,复制长度以len小的为准。两个slice可指向同一底层数组,允许元素区间重叠。
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := data[8:]
s2 := data[:5]
fmt.Println(s1,s2) // [8 9] [0 1 2 3 4]
copy(s2, s1)
fmt.Println(s1,s2) // [8 9] [8 9 2 3 4]
应及时将所需数据copy到较小的slice,以便释放超大号底层数组内存。
常规slice,data[6:8],从第6位到第8位(返回6,7),长度len为2,最大可扩充长度cap为4(6-9)
另一种写法:data[:6:8]每个数字前都有个冒号,slice内容为data从0到第6位,长度len为6,最大扩充项cap设置为8
a[x:y:z]切片内容[x:y],切片长度: y-x,切片容量:z-x
遍历数组或slice:
for i := 0; i < len(data); i++ {
println(i, data[i])
}
for i := range data {
println(i, data[i])
}
for i, v := range data {
println(i, v)
}
string底层就是一个byte的数组,因此,也可以进行切片操作。
字符串下标遍历访问的是单个byte,range遍历访问的是单个rune。
var s = `你好World`
for i := 0; i < len(s); i++ {
fmt.Printf("%d: %d (%c) \n", i, s[i], s[i])
} /* 下标遍历输出:
0: 228 (ä)
1: 189 (½)
2: 160 ( )
3: 229 (å)
4: 165 (¥)
5: 189 (½)
6: 87 (W)
7: 111 (o)
8: 114 (r)
9: 108 (l)
10: 100 (d)
*/
for i, c := range s {
fmt.Printf("%d: %d (%c) \n", i, c, c)
} /* range遍历输出:
0: 20320 (你)
3: 22909 (好)
6: 87 (W)
7: 111 (o)
8: 114 (r)
9: 108 (l)
10: 100 (d)
*/