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