前言
数组的长度是声明的时候就固定好的,后面不可能变大,而且长度和容量相等。
切片的长度和容量后面可以随着元素增多而增长,但是容量不可能小于长度。
正文
声明&初始化
在 Go 中声明即初始化,如果在声明的时候没有初始化值,那么就会赋值为声明类型的「零值」。
func TestDemo1(t *testing.T) {
// 数组
var array1 [5]int // 只需设置长度,后面不可变
var array2 = new([5]int) // 返回指针
// 切片
var slice1 []int
var slice2 = make([]int, 5, 5) // 设置长度、容量,后面可变
t.Log("array1 val:", array1) // [0 0 0 0 0]
t.Log("array1 len:", len(array1)) // 5
t.Log("array1 cap:", cap(array1)) // 5
fmt.Println("")
t.Log("array2 val:", array2) // &[0 0 0 0 0]
t.Log("array2 len:", len(array2)) // 5
t.Log("array2 cap:", cap(array2)) // 5
fmt.Println("")
t.Log("slice1 val:", slice1) // []
t.Log("slice1 len:", len(slice1)) // 0
t.Log("slice1 cap:", cap(slice1)) // 0
fmt.Println("")
t.Log("slice2 val:", slice2) // [0 0 0 0 0]
t.Log("slice2 len:", len(slice2)) // 5
t.Log("slice2 cap:", cap(slice2)) // 5
}
在声明的时候就初始化:
func TestDemo2(t *testing.T) {
// 数组
var array1 = [5]int{4: 1, 2: 5}
var array2 = [...]int{4: 1, 2: 5}
// 切片
var slice1 = []int{4: 1, 2: 5}
var slice2 = array1[:] // 从数组截取来的切片
t.Log("array1 val:", array1) // [0 0 5 0 1]
t.Log("array1 len:", len(array1)) // 5
t.Log("array1 cap:", cap(array1)) // 5
fmt.Println("")
t.Log("array2 val:", array2) // [0 0 5 0 1]
t.Log("array2 len:", len(array2)) // 5
t.Log("array2 cap:", cap(array2)) // 5
fmt.Println("")
t.Log("slice1 val:", slice1) // [0 0 5 0 1]
t.Log("slice1 len:", len(slice1)) // 5
t.Log("slice1 cap:", cap(slice1)) // 5
fmt.Println("")
t.Log("slice2 val:", slice2) // [0 0 5 0 1]
t.Log("slice2 len:", len(slice2)) // 5
t.Log("slice2 cap:", cap(slice2)) // 5
}
添加&更新元素值
数组因为长度固定,且的值都是初始化好了的,所以只有更新。
切片更新操作和数据一样,只不过新增元素只能通过 append() 方法。
append():将元素追加大切片的末尾,如果容量不够,会进行扩容。
func TestDemo3(t *testing.T) {
// 数组
var array1 = [5]int{4: 1, 2: 5}
array1[0] = 100 // 更新
array1[4] = 100 // 更新
// 切片
var slice1 = []int{4: 1, 2: 5}
array1[4] = 100 // 更新
//array1[5] = 100 // 报错
slice1 = append(slice1, 1) // 切片增加元素只能使用此方法
t.Log("array1 val:", array1) // [100 0 5 0 100]
t.Log("array1 len:", len(array1)) // 5
t.Log("array1 cap:", cap(array1)) // 5
fmt.Println("")
t.Log("slice1 val:", slice1) // [0 0 5 0 1 1]
t.Log("slice1 len:", len(slice1)) // 6
t.Log("slice1 cap:", cap(slice1)) // 10
}
表达式
数组与切片,都可以使用表达式截取,截取之后的数据它的类型为切片。
func TestDemo4(t *testing.T) {
array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
slice1 := array1[3:] // 从 index 3 取到 index end
t.Log("slice1 val:", slice1) // [3 0 5 6 2 8 4]
t.Log("slice1 len:", len(slice1)) // 7
t.Log("slice1 cap:", cap(slice1)) // 7
fmt.Println("")
slice2 := array1[3:4] // 从 index 3 取到 index 4
t.Log("slice2 val:", slice2) // [3]
t.Log("slice2 len:", len(slice2)) // 1
t.Log("slice2 cap:", cap(slice2)) // 7
fmt.Println("")
slice3 := array1[3:6:6] // 从 index 3 取到 index 6,容量取到 index 6
t.Log("slice3 val:", slice3) // [3 0 5]
t.Log("slice3 len:", len(slice3)) // 3
t.Log("slice3 cap:", cap(slice3)) // 3
fmt.Println("")
slice4 := array1[3:6:9] // 从 index 3 取到 index 6,容量取到 index 9
t.Log("slice4 val:", slice4) // [3 0 5]
t.Log("slice4 len:", len(slice4)) // 3
t.Log("slice4 cap:", cap(slice4)) // 6
}
遍历
使用 for、range
func TestDemo5(t *testing.T) {
array1 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
slice1 := make([]int, 5, 5)
for k, v := range array1 {
fmt.Println(k, "-", v)
}
fmt.Println()
for k, v := range slice1 {
fmt.Println(k, "-", v)
}
}
比较
数组与数组可以使用 == 比较,不能与 nil 比较
切片与切片不能使用 == 比较,可以使用 reflect.DeepEqual 比较,可以与 nil 比较
func TestDemo6(t *testing.T) {
array1 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
array2 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 9}
array3 := [...]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 9}
t.Logf("array1 == array2 %t\n", array1 == array2) // false
t.Logf("array2 == array3 %t\n", array2 == array3) // true
//t.Logf("%t\n", array2 == nil) // 会报错,数组不能与nil比
slice1 := make([]int, 5, 5)
var slice2 []int
slice3 := []int{4: 0}
// t.Logf("%t\n", slice1 == slice2) // 会报错,切片与切片不能比
t.Logf("slice1 == nil %t\n", slice1 == nil) // false
t.Logf("slice2 == nil %t\n", slice2 == nil) // true
t.Logf("slice3 == nil %t\n", slice3 == nil) // false
t.Logf("slice1 == slice2 %t\n", reflect.DeepEqual(slice1, slice2)) // false
t.Logf("slice2 == slice3 %t\n", reflect.DeepEqual(slice2, slice3)) // false
t.Logf("slice1 == slice3 %t\n", reflect.DeepEqual(slice1, slice3)) // true
}
删除
需使用 append()、切片表达式 结合来完成
func TestDemo7(t *testing.T) {
slice1 := []int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
slice1 = append(slice1[:2], slice1[3:]...)
t.Log(slice1)
}
扩展
数组与切片的关系
数组为值类型,切片为引用类型,他们又有何关系呢?
程序示例:
func TestDemo8(t *testing.T) {
array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
slice1 := array1[:]
t.Log("slice1 val:", slice1) // [9 1 7 3 0 5 6 2 8 4]
t.Log("slice1 len:", len(slice1)) // 10
t.Log("slice1 cap:", cap(slice1)) // 10
array1[9] = 96969696 // array1 的修改会影响到 slice1
fmt.Println("")
t.Log("slice1 val:", slice1) // [9 1 7 3 0 5 6 2 8 96969696]
t.Log("slice1 len:", len(slice1)) // 10
t.Log("slice1 cap:", cap(slice1)) // 10
}
在这个示例程序中,可以说 slice1 是 array1 的引用。
不光是在示例程序中,这种在数组上通过表达式截取出的切片,为数组的引用,就算在程序中,直接声明一个新切片(var slice1 []int),在切片的底层实现,其实也是引用了一个数组。
他们的关系就是:数组是切片的底层实现,切片是数组的引用。
切片扩容
在示例程序 TestDemo8 中,slice1 会一直引用 array1 么?
一般情况下是这样,但有种情况下引用会发生变化,就是在 slice 发生扩容的情况下
func TestDemo9(t *testing.T) {
array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
slice1 := array1[:] // 从 array1 截取出 slice1
t.Log("slice1 val:", slice1) // [9 1 7 3 0 5 6 2 8 4]
t.Log("slice1 len:", len(slice1)) // 10
t.Log("slice1 cap:", cap(slice1)) // 10
slice1 = append(slice1, 9) // 进行扩容后,slice1 指向了新的底层数组,不在是 array1 的引用
array1[9] = 96969696
fmt.Println("")
t.Log("slice1 val:", slice1) // [9 1 7 3 0 5 6 2 8 4 9]
t.Log("slice1 len:", len(slice1)) // 11
t.Log("slice1 cap:", cap(slice1)) // 20
}
当切片添加新元素,发现容量不够时,会开辟一个新的底层数组,然后把旧数组的数据和添加的新元素一并拷贝到新数组中。
扩容策略:
- 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
- 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)
- 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)
深复制
靠扩容解决引用问题,显得不是那么优雅。
可以使用 copy() 进行深复制
func TestDemo10(t *testing.T) {
array1 := [10]int{9, 1, 7, 3, 0, 5, 6, 2, 8, 4}
var slice1 = make([]int, 10, 10)
copy(slice1, array1[:]) // 深复制,slice1 不会引用 array1
array1[9] = 96969696
t.Log("slice1 val:", slice1) // [9 1 7 3 0 5 6 2 8 4]
t.Log("slice1 len:", len(slice1)) // 10
t.Log("slice1 cap:", cap(slice1)) // 10
fmt.Println("")
t.Log("array1 val:", array1) // [9 1 7 3 0 5 6 2 8 96969696]
t.Log("array1 len:", len(array1)) // 10
t.Log("array1 cap:", cap(array1)) // 10
}
数组
一段连续的内存空间。
make
make 只能用于 slice、map、channel,返回的初始化后的(非零)值。
引用类型
- 切片
- 字典
- 通道
- 函数
值类型
- 数组
- 基础数据类型
- 结构体类型
总结
- 切片是数组的引用,数组是切片的底层实现。
- 数组的长度(len)等于容量(cap),切片的长度(len)小于等于容量(cap)。
- 数组声明的时候默认就会初始化,值为类型的「零值」;切片声明的时候,如果不初始化,值是 nil。
- 使用 copy() 深复制解决引用问题。
Sown专栏地址:https://segmentfault.com/blog/sown
有疑问加站长微信联系(非本文作者)