Golang 基础 Part 1: 数组和切片
文章讨论了关于数组和切片在 Golang 中的基础知识(切片的本质、扩容以及值传递问题)、怎样在 Golang 中应用他们以及使用切片需要注意的地方。
1. 切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息
- 底层数组的指针
- 切片的长度(len)
- 切片的容量(cap)
举个例子,现在有一个数组 a := [8]int {0,1,2,3,4,5,6,7}
,切片 s1 := a[:5]
,相应示意图如下
切片 s2 := a[3:6]
,相应示意图如下:
2. 切片的扩容
其中:
- T:切片的元素类型
- size:切片中元素的数量
- cap:切片的容量
举例
1// make()函数创建切片
2fmt.Println()
3var slices = make([]int, 4, 8)
4//[0 0 0 0]
5fmt.Println(slices)
6// 长度:4, 容量8
7fmt.Printf("长度:%d, 容量%d", len(slices), cap(slices))
需要注意的是,golang 中没办法通过下标来给切片扩容,如果需要扩容,需要用到 append
1slices2 := []int{1,2,3,4}
2slices2 = append(slices2, 5)
3fmt.Println(slices2)
4// 输出结果 [1 2 3 4 5]
同时切片还可以将两个切片进行合并
1// 合并切片
2slices3 := []int{6,7,8}
3slices2 = append(slices2, slices3...)
4fmt.Println(slices2)
5// 输出结果 [1 2 3 4 5 6 7 8]
需要注意的是,切片会有一个扩容操作,当元素存放不下的时候,会将原来的容量扩大两倍。
3. 切片的传递问题
切片本身传递给函数形参时是引用传递,但 append
后,切片长度变化时会被重新分配内存,而原来的切片还是指向原来地址,致使与初始状况传进来的地址不一样,要想对其值有改变操作,需使用指针类型操作。
我们来看一道 leetcode 78:
1
2package main
3
4import "fmt"
5
6func helper(nums []int, res *[][]int, tmp []int, level int) {
7 if len(tmp) <= len(nums) {
8 //长度一样的tmp用的是同一个地址,故可能会存在覆盖值得情况,
9 // 长度不一样时重新开辟空间,将已有得元素复制进去
10 //*res = append(*res, tmp) 如此处,最终长度为1的tmp会被最后3这个元素全部覆盖
11 //以下相当于每次重新申请内存,使其指向的地址不一样,解决了最后地址一样的元素值被覆盖的状态状态
12 var a []int
13 a = append(a, tmp[:] ...)
14 //res = append(res, a) 如果此处不是指针引用传递,在append后,res重新分配内存,与之前传进来的res地址不一样,最终res仍为空值
15 *res = append(*res, a)
16 }
17 //fmt.Println(*res, "--->", tmp)
18 for i := level; i < len(nums); i ++ {
19 tmp = append(tmp, nums[i])
20 helper(nums, res, tmp, i + 1)
21 tmp = tmp[:len(tmp) - 1] //相当于删除tmp末位的最后一个元素
22 }
23}
24
25func subsets(nums []int) [][]int {
26 if len(nums) == 0 {
27 return nil
28 }
29 var res [][]int
30 var tmp []int
31 helper(nums, &res, tmp, 0)
32 return res
33}
34
35
36func main() {
37 pre := []int{1, 2, 3}
38 fmt.Println(subsets(pre))
39}
40//错误结果:[[] [3] [1 3] [1 2 3] [1 3] [3] [2 3] [3]], 可以看出,长度为1的切片都被3覆盖了,这由于它们的地址不一样
41//正确输出:[[] [1] [1 2] [1 2 3] [1 3] [2] [2 3] [3]], 这是因为每次都为a分配内存,其地址都与之前的不一样,故最终的值没有被覆盖