#切片
1. 切片的创建和数组创建的区别
切片的创建不用指定数值大小。 创建方式:
- var或者
:= - make方式
- var或者
:=初始化的方式:- 通过初始化切片变量,然后使用append函数往切片中添加元素。
package main
import "fmt"
func main() {
var slice []int // 创建切片类型
slice = append(slice, 1)
slice = append(slice, 2)
slice = append(slice, 3)
slice = append(slice, 4)
slice = append(slice, 5)
fmt.Println(slice, len(slice), cap(slice))
}
/*
[1 2 3 4 5] 5 8
*/
- 问题:为什么最后打印出来的切片的容量为8?
- 提示:好像是跟append有关系。
- 解答参见文章第6节:6. 对切片进行动态扩容为什么会改变底层原数组
- make函数的方式
- 第二个参数为len:切片元素长度。
- 第三个参数为cap:切片容量,也就是底层数组的大小。
package main
import "fmt"
func main() {
slice1 := make([]int, 5) // 指定len为5,cap容量默认也为5
slice2 := make([]int, 5, 8) // 指定len为5,cap为8
slice1[0] = 11
fmt.Println(slice1, len(slice1), cap(slice1))
fmt.Println(slice2, len(slice2), cap(slice2))
}
2. 切片类型如果不初始化,声明后直接使用会怎么样?
如果不初始化,切片变量就没有分配内存空间,容量为0。此时的切片变量等于nil空。
如果不初始化直接使用,会报数组访问越界的panic。
3. 如何避免切片未初始化的风险?
检查切片变量是否为空,为空则进行初始化。 两种方式:
- 直接make初始化。
- 使用append添加元素,会对切片进行扩容初始化
func main() {
var slice1 []int
fmt.Println(cap(slice1))
fmt.Println(slice1 == nil)
if slice1 == nil {
slice1 = make([]int, 10)
}
slice1[0] = 19
fmt.Println(slice1)
}
/*
0
true
[19 0 0 0 0 0 0 0 0 0]
*/
4. 切片的本质
切片底层指向的是一个数组,使用切片其实就是数组的引用。 切片底层维护着三个属性:
- 指针(执行一个数组)
- 长度len
- 容量cap
5. 对切片进行切片化的本质
对切片化得到的不同切片,底层其实指向的是同一个数组,只是指针指向的数组位置不同。
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3, 4, 5}
fmt.Println(slice1, len(slice1), cap(slice1))
slice2 := slice1[1:3] // 切片化
fmt.Println(slice2, len(slice2), cap(slice2))
slice2[0] = 100 // 更改元素是否会影响到slice1
fmt.Println(slice1, len(slice1), cap(slice1))
fmt.Println(slice2, len(slice2), cap(slice2))
}
/*
[1 2 3 4 5] 5 5
[2 3] 2 4
[1 100 3 4 5] 5 5
[100 3] 2 4
*/
6. 对切片进行动态扩容为什么会改变底层原数组
动态扩容后会发生什么?
- 容量翻倍增长
- 更改底层数组(底层数组替换为新的数组)
package main
import "fmt"
func main() {
var slice1 []int // 定义一个切片,但不初始化
fmt.Println(slice1, len(slice1), cap(slice1))
slice1 = append(slice1, 1)
fmt.Println(slice1, len(slice1), cap(slice1))
slice1 = append(slice1, 2)
fmt.Println(slice1, len(slice1), cap(slice1))
slice1 = append(slice1, 3)
fmt.Println(slice1, len(slice1), cap(slice1))
slice1 = append(slice1, 4)
fmt.Println(slice1, len(slice1), cap(slice1))
slice1 = append(slice1, 5)
fmt.Println(slice1, len(slice1), cap(slice1))
}
/*
[] 0 0
[1] 1 1
[1 2] 2 2
[1 2 3] 3 4
[1 2 3 4] 4 4
[1 2 3 4 5] 5 8
*/
由打印出来的cap值可以看出容量是翻倍扩容的。
package main
import "fmt"
func main() {
var slice1 []int
slice1 = append(slice1, 1)
slice1 = append(slice1, 2)
slice1 = append(slice1, 3)
slice1 = append(slice1, 4)
fmt.Println(slice1, len(slice1), cap(slice1))
slice2 := slice1[1:3]
slice2[0] = 100
fmt.Println(slice1, len(slice1), cap(slice1))
fmt.Println(slice2, len(slice2), cap(slice2))
slice1 = append(slice1, 5) // 此时slice1容量扩充,是否会改变底层数组呢?
slice2[0] = 99
fmt.Println(slice1, len(slice1), cap(slice1))
fmt.Println(slice2, len(slice2), cap(slice2))
}
/*
[1 2 3 4] 4 4
[1 100 3 4] 4 4
[100 3] 2 3
[1 100 3 4 5] 5 8
[99 3] 2 3
*/
- 可以看到扩容后,改变slice2元素,slice1不受影响,说明底层这两个切片已经不指向同一个数组了。
- 说明每次切片扩容后,底层都会重新创建一个数组。
7. 切片在实际应用中,怎么用更好,为什么?
- 函数参数用切片,而不要用数组。
- 切片其实就是引用传值,不涉及拷贝,性能更好。
- 如果不知道切片长度,初始化时尽量给定一个容量:
make([]int, 0, 20)- 避免频繁扩容影响性能。
- 如果知道切片长度,初始化可以只指定长度:
make([]int, 20)- 指定len后,cap和len相同。
8. 如何初始化一个n×n的多维数组?
arr := make([][]int, n)
for i := 0;i < n;i ++ {
arr[i] = make([]int, n)
}