数组
和以往认知的数组有很大不同。
- 数组是值类型,赋值和传参会复制整个数组,而不是指针。
- 数组长度必须是常量,且是类型的组成部分。
[2]int
和[3]int
是不同类型。 - 支持 "=="、"!=" 操作符,因为内存总是被初始化过的。(因为是值类型,所以如果两个数组内元素顺序和值相同,则两个数组相同)
- 指针数组
[n]*T
,数组指针*[n]T
。
a := [3]int{1,2,3}
b := [3]int{1,2,3}
fmt.Printf("%p %p\n",&a,&b)//0xc00000e380 0xc00000e3a0 地址位置不同
fmt.Println(a==b)//true
数组初始化
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4:200} // 使用索引号初始化元素。
//使用符合语句初始化匿名结构体数组
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}
多维数组初始化
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
数组是值类型!
值拷贝行为会造成性能问题,通常会建议使用 slice
,或数组指针。
package main
import "fmt"
func test(x [2]int) {
fmt.Printf("x: %p\n", &x)
x[1] = 1000
}
func main() {
a := [2]int{}
fmt.Printf("a: %p\n", &a) //a: 0xc000018040
test(a) //x: 0xc000018060 传入的是原数组的拷贝,而非之前的数组
fmt.Println(a) //[0 0]
}
内置函数 len
和 cap
都返回数组长度 (元素数量)。
切片
需要说明,slice
并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
//slice内部结构
struct Slice { // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};
切片的特点:
- 引用类型。但自身是结构体,值拷贝传递。
- 属性
len
表示可用元素数量,读写操作不能超过该限制。 - 属性
cap
表示最大扩张容量,不能超出数组限制。 - 如果
slice == nil
,那么len
、cap
结果都等于0
。
切片与数组的关系
data := [...]int{0, 1, 2, 3, 4, 5, 6}
slice := data[1:4:5] // [low : high : max]
通过数组创建切片
使用语法 数组名[low:high:max]
来创建切片,规律如下:
对于数组:data := [...]int{0,1,2,3,4,5,6,7,8,9}
切片表达式 | 切片内容 | 切片长度len | 切片容量cap | 备注 |
---|---|---|---|---|
data[:6:8] | 0 1 2 3 4 5 | 6 | 8 | 省略 low |
data[5:] | 5 6 7 8 9 | 5 | 5 | 省略high、max |
data[:3] | 0 1 2 | 3 | 10 | 省略low、max |
data[:] | 0 1 2 3 4 5 6 7 8 9 | 10 | 10 | 全部省略 |
data := [...]int{1,2,3,4,5}
s1 := data[:]
data[2] = 100
fmt.Println(s1) //[1 2 100 4 5]
可见使用这种方法创建的切片的数据数组就是原数组
其实这种方法实际使用不多
reslice,用切片创建切片
这种方法在代码上和上一种很相似
所谓 reslice
,是基于已有 slice
创建新 slice
对象,以便在 cap
允许范围内调整属性。
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[2:5] // [2 3 4]
s2 := s1[2:6:7] // [4 5 6 7]
s3 := s2[3:6] // Error
新对象依旧指向原底层数组。
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[2:5] // [2 3 4]
s1[2] = 100
s2 := s1[2:6] // [100 5 6 7]
s2[3] = 200
fmt.Println(s) //[0 1 2 3 100 5 6 200 8 9]
直接创建切片
直接创建 slice
对象,自动分配底层数组。
s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。
s2 := make([]int, 6, 8) // 使用 make 创建,指定 len 和 cap 值。
s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
使用 make
动态创建 slice
,避免了数组必须用常量做长度的麻烦,在实际使用中,这种创建切片的方式更为常用
切片的读写操作
和使用数组一样,但需要注意索引号的差别,即切片和数组的下标是相互独立的
data := [...]int{0, 1, 2, 3, 4, 5}
s := data[2:4] // {2,3,4}
s[0] += 100 // {102,3,4}
s[1] += 200 // {102,203,4}
fmt.Println(s) // {102,203,4}
fmt.Println(data) // {0,1,102,203,4,5}
当然也可以用指针直接访问底层数组
s := []int{0, 1, 2, 3}
p := &s[2] // *int, 获取底层数组元素指针。
*p += 100
s[2] += 100 //也可以直接使用数组的方式操作
可直接修改 struct array
/slice
成员。
d := [5]struct {
x int
}{}
s := d[:]
d[1].x = 10
s[2].x = 20
fmt.Println(d) // [{0} {10} {20} {0} {0}]
fmt.Printf("%p, %p\n", &d, &d[0]) // 0x20819c180, 0x20819c180
二(多)维切片
与多维数组一样 [][]T
,是指元素类型为[]T
的切片
data := [][]int{
[]int{1, 2, 3},
[]int{100, 200},
[]int{11, 22, 33, 44},
}
或者使用切片嵌套赋值的方式
multi := make([][]int,10)
for i := 1; i < 10; i++ {
multi[i-1] = make([]int,10)
for j := 1; j <= i; j++ {
multi[i-1][j-1] = i*j
}
}
切片的常用方法
append
func append(slice []Type, elems ...Type) []Type
向 slice
尾部添加数据,返回新的 slice
对象。
s := make([]int, 0, 5)
fmt.Printf("%p\n", &s) //0x210230000
s2 := append(s, 1)
fmt.Printf("%p\n", &s2) //0x210230040
fmt.Println(s, s2) //[] [1]
说明append前后的两个切片不是同一个切片
//如果 cap 够用(此处cap为5,但只放入了5个元素)
s1 := make([]int, 0, 5)
s1 = append(s1,1)
fmt.Printf("%p\n", &s1[0]) //0xc0000c8030
s2 := append(s1, 2)
fmt.Printf("%p\n", &s2[0]) //0xc0000c8030
//如果 cap 不够用(此处cap为1,但放入了两个元素)
s1 := make([]int, 0, 1)
s1 = append(s1,1)
fmt.Printf("%p\n", &s1[0])//0xc0000a0068
s2 := append(s1, 2)
fmt.Printf("%p\n", &s2[0])//0xc0000a00a0
可见:
如果cap够用,则append前后使用的是相同的数据数组
如果cap不够用,则append前后使用不同的数据数组
注意:是否使用新的数据数组与数据数组容量无关,之和切片的cap有关,所以会出现即便数据数组没满但超过cap限制导致重新分配数据数组
最佳实践
通常以 2
倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len
属性,改用索引号进行操作。及时释放不再使用的 slice
对象,避免持有过期数组,造成 GC
无法回收。
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 50; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
/*
输出:
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
*/
copy
func copy(dst, src []Type) int
函数 copy
在两个 slice
间复制数据
srcSlice,其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice)
复制长度以 len
小的为准,函数的返回值就是复制的长度。
两个 slice
可指向同一底层数组,允许元素区间重叠。
package main
import "fmt"
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := data[8:] //[8 9]
s2 := data[:5]// [0 1 2 3 4]
copyLen := copy(s2, s) // dst:s2, src:s 从s往s2复制,复制长度为2
fmt.Println(copyLen) //2
fmt.Println(s)//[8 9]
fmt.Println(s2)//[8 9 2 3 4]
fmt.Println(data)//[8 9 2 3 4 5 6 7 8 9]
}
参考
Q.E.D.